unit uDZGrids;

interface

uses
  Consts, SysUtils, Classes, Controls, Forms, Grids, Graphics,
  Math, StdCtrls, Mask, Windows, Messages, Dialogs,
  uDZSysUtils, uDZPersistent, uDZRangeList;

type
  TDzGridAxisDrawInfo = record
    EffectiveLineWidth: Integer;
    FixedBoundary: Integer;
    GridBoundary: Integer;
    GridExtent: Integer;
    LastFullVisibleCell: Longint;
    FullVisBoundary: Integer;
    FixedCellCount: Integer;
    FirstGridCell: Integer;
    GridCellCount: Integer;
    GetExtent: TGetExtentsFunc;
  end;

  TDzGridDrawInfo = record
    Horz, Vert: TDzGridAxisDrawInfo;
  end;

  TDzCustomGrid = class;
  TDzCustomColumnGrid = class;

  TDzGridColumnContentType = (
    gcctPlainText,
    gcctHTML,
    gcctPicture,
    gcctCheckBox
  );

  TDzGridColumn = class(TCollectionItem)
  private
    fCaption: string;
    fFieldName: string;
    fWidth: Integer;
    fHint: string;
    fContentType: TDzGridColumnContentType;
    fContentAlign: TAlignment;
    fTitleAlign: TAlignment;
    fSortDirection: TSortDirection;
    fFieldInfo: Pointer;
    procedure SetCaption(const Value: string);
    procedure SetContentAlign(const Value: TAlignment);
    procedure SetFieldName(const Value: string);
    procedure SetHint(const Value: string);
    procedure SetSortDirection(const Value: TSortDirection);
    procedure SetTitleAlign(const Value: TAlignment);
    procedure SetWidth(const Value: Integer);
    procedure SetContentType(const Value: TDzGridColumnContentType);
  protected
    function GetDisplayName: string; override;
  public
    constructor Create(Collection: TCollection); override;
    procedure Assign(Source: Classes.TPersistent); override;
    property FieldInfo: Pointer read fFieldInfo write fFieldInfo;
  published
    property Caption: string read fCaption write SetCaption;
    property FieldName: string read fFieldName write SetFieldName;
    property Width: Integer read fWidth write SetWidth default 60;
    property Hint: string read fHint write SetHint;
    property ContentType: TDzGridColumnContentType
      read fContentType write SetContentType;
    property ContentAlign: TAlignment
      read fContentAlign write SetContentAlign default taCenter;
    property TitleAlign: TAlignment
      read fTitleAlign write SetTitleAlign default taCenter;
    property SortDirection: TSortDirection
      read fSortDirection write SetSortDirection default sdNone;
  end;

  TDzGridColumnClass = class of TDzGridColumn;

  TDzGridColumns = class(TCollection)
  private
    fGrid: TDzCustomColumnGrid;
  private
    function GetItem(Index: Integer): TDzGridColumn;
    procedure SetItem(Index: Integer; Value: TDzGridColumn);
  protected
    function GetOwner: Classes.TPersistent; override;
    procedure Update(Item: TCollectionItem); override;
  public
    constructor Create(Grid: TDzCustomColumnGrid);
    function Add: TDzGridColumn;
    function AddItem(Item: TDzGridColumn; Index: Integer): TDzGridColumn;
    function Insert(Index: Integer): TDzGridColumn;
    function ColumnByName(const ColName: string): TDzGridColumn;
    property Items[Index: Integer]: TDzGridColumn
      read GetItem write SetItem; default;
  end;

  TDzInplaceEdit = class(TCustomMaskEdit)
  private
    FGrid: TDzCustomGrid;
    FClickTime: Longint;
    procedure InternalMove(const Loc: TRect; Redraw: Boolean);
    procedure SetGrid(Value: TDzCustomGrid);
    procedure CMShowingChanged(var Message: TMessage); message
      CM_SHOWINGCHANGED;
    procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMPaste(var Message); message WM_PASTE;
    procedure WMCut(var Message); message WM_CUT;
    procedure WMClear(var Message); message WM_CLEAR;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure DblClick; override;
    function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
      MousePos: TPoint): Boolean; override;
    function EditCanModify: Boolean; override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyPress(var Key: Char); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); override;
    procedure BoundsChanged; virtual;
    procedure UpdateContents; virtual;
    procedure WndProc(var Message: TMessage); override;
    property Grid: TDzCustomGrid read FGrid;
  public
    constructor Create(AOwner: TComponent); override;
    procedure Deselect;
    procedure Hide;
    procedure Invalidate; reintroduce;
    procedure Move(const Loc: TRect);
    function PosEqual(const Rect: TRect): Boolean;
    procedure SetFocus; reintroduce;
    procedure UpdateLoc(const Loc: TRect);
    function Visible: Boolean;
  end;

  TGridCellNotifyEvent = procedure(Sender: TObject;
    ACol, ARow: Integer) of object;

  TDzCustomGrid = class(TCustomControl)
  private
    fAnchor: TGridCoord;
    FCurrent: TGridCoord;
    FTopLeft: TGridCoord;
    FMaxTopLeft: TGridCoord;
    FBorderStyle: TBorderStyle;
    FReadOnly: Boolean;
    FColCount: Longint;
    FRowCount: Longint;
    FFixedCols: Integer;
    FFixedRows: Integer;
    FDefaultColWidth: Integer;
    FDefaultRowHeight: Integer;
    FTabStops: Pointer;
    FFixedColor: TColor;
    FHighLightColor: TColor;
    FHighLightTextColor: TColor;
    FOptions: TGridOptions;
    FScrollBars: TScrollStyle;
    FDefaultDrawing: Boolean;
    FFocusRect: Boolean;
    FEditorMode: Boolean;
    FColWidths: Pointer;
    FRowHeights: Pointer;
    FCurrentDrawInfo: TDzGridDrawInfo;
    FSizingIndex: Longint;
    FSizingPos: Integer;
    FSizingOfs: Integer;
    FMoveIndex: Integer;
    FMovePos: Integer;
    FHitTest: TPoint;
    FInplaceEdit: TDzInplaceEdit;
    FInplaceCol: Integer;
    FInplaceRow: Integer;
    FColOffset: Integer;
    FOnDblClickCell: TGridCellNotifyEvent;
    FOnClickCell: TGridCellNotifyEvent;
    FVertLineWidth: Integer;
    FHorzLineWidth: Integer;
    procedure DoSelect(RowIndex: Integer; Shift: TShiftState);
    function CalcCoordFromPoint(X, Y: Integer;
      const DrawInfo: TDzGridDrawInfo): TGridCoord;
    procedure CalcAxis(var AxisInfo: TDzGridAxisDrawInfo; UseExtent: Integer);
    procedure CalcDrawInfoXY(var DrawInfo: TDzGridDrawInfo;
      UseWidth, UseHeight: Integer);
    function CalcMaxCell(const Axis: TDzGridAxisDrawInfo;
      Start: Integer): Integer;
    function CalcMaxTopLeft(const BottomRight: TGridCoord;
      const DrawInfo: TDzGridDrawInfo): TGridCoord;
    procedure CancelMode;
    procedure ChangeSize(NewColCount, NewRowCount: Longint);
    procedure ClampInView(const Coord: TGridCoord);
    procedure DrawSizingLine(const DrawInfo: TDzGridDrawInfo);
    procedure DrawMove;
    procedure GridRectToScreenRect(GridRect: TGridRect;
      var ScreenRect: TRect; IncludeLine: Boolean);
    procedure Initialize;
    procedure InvalidateRect(ARect: TGridRect);
    procedure UpdateDrawInfo;
    procedure UpdateMaxTopLeft;
    procedure ModifyScrollBar(ScrollBar, ScrollCode, Pos: Integer;
      UseRightToLeft: Boolean);
    procedure MoveAdjust(var CellPos: Longint; FromIndex, ToIndex: Longint);
    procedure MoveAnchor(const NewAnchor: TGridCoord);
    procedure MoveAndScroll(Mouse, CellHit: Integer; var DrawInfo:
      TDzGridDrawInfo;
      var Axis: TDzGridAxisDrawInfo; Scrollbar: Integer; const MousePt: TPoint);
    procedure MoveCurrent(ACol, ARow: Longint; MoveAnchor, Show: Boolean);
    procedure MoveTopLeft(ALeft, ATop: Longint);
    procedure ResizeCol(Index: Longint; OldSize, NewSize: Integer);
    procedure ResizeRow(Index: Longint; OldSize, NewSize: Integer);
    procedure SelectionMoved(const OldSel: TGridRect);
    procedure ScrollDataInfo(DX, DY: Integer; var DrawInfo: TDzGridDrawInfo);
    procedure TopLeftMoved(const OldTopLeft: TGridCoord);
    procedure UpdateScrollPos;
    function GetColWidths(Index: Longint): Integer;
    function GetRowHeights(Index: Longint): Integer;
    function GetSelection: TGridRect;
    function GetTabStops(Index: Longint): Boolean;
    function GetVisibleColCount: Integer;
    function GetVisibleRowCount: Integer;
    function IsActiveControl: Boolean;
    procedure ReadColWidths(Reader: TReader);
    procedure ReadRowHeights(Reader: TReader);
    procedure SetBorderStyle(Value: TBorderStyle);
    procedure SetCol(Value: Longint);
    procedure SetColCount(Value: Longint);
    procedure SetColWidths(Index: Longint; Value: Integer);
    procedure SetDefaultColWidth(Value: Integer);
    procedure SetDefaultRowHeight(Value: Integer);
    procedure SetEditorMode(Value: Boolean);
    procedure SetFixedColor(Value: TColor);
    procedure SetFixedCols(Value: Integer);
    procedure SetFixedRows(Value: Integer);
    procedure SetLeftCol(Value: Longint);
    procedure SetOptions(Value: TGridOptions);
    procedure SetRow(Value: Longint);
    procedure SetRowCount(Value: Longint);
    procedure SetRowHeights(Index: Longint; Value: Integer);
    procedure SetScrollBars(Value: TScrollStyle);
    procedure SetSelection(Value: TGridRect);
    procedure SetTabStops(Index: Longint; Value: Boolean);
    procedure SetTopRow(Value: Longint);
    procedure UpdateEdit;
    procedure UpdateText;
    procedure WriteColWidths(Writer: TWriter);
    procedure WriteRowHeights(Writer: TWriter);
    procedure WMCreate(var Msg: TWMCreate); message WM_CREATE;
    procedure CMCancelMode(var Msg: TMessage); message CM_CANCELMODE;
    procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
    procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED;
    procedure CMDesignHitTest(var Msg: TCMDesignHitTest);
      message CM_DESIGNHITTEST;
    procedure CMWantSpecialKey(var Msg: TCMWantSpecialKey);
      message CM_WANTSPECIALKEY;
    procedure CMShowingChanged(var Message: TMessage);
      message CM_SHOWINGCHANGED;
    procedure WMChar(var Msg: TWMChar); message WM_CHAR;
    procedure WMCancelMode(var Msg: TWMCancelMode); message WM_CANCELMODE;
    procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;
    procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
    procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS;
    procedure WMLButtonDown(var Message: TMessage); message WM_LBUTTONDOWN;
    procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
    procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR;
    procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS;
    procedure WMSize(var Msg: TWMSize); message WM_SIZE;
    procedure WMTimer(var Msg: TWMTimer); message WM_TIMER;
    procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;
    function GetSelRow(index: Integer): Integer;
    function GetSelRowCount: Integer;
    function ScrollBarVisible(Code: Word): Boolean;
    procedure SetHighLightColor(const Value: TColor);
    procedure SetHightLightTextColor(const Value: TColor);
    procedure SetVertLineWidth(const Value: Integer);
    procedure SetHorzLineWidth(const Value: Integer);
  protected
    FGridState: TGridState;
    FMouseDownCoord: TGridCoord;
    FSaveCellExtents: Boolean;
    DesignOptionsBoost: TGridOptions;
    VirtualView: Boolean;
    FSelRows: TDzIntRangeList;
    property OnDblClickCell: TGridCellNotifyEvent
      read FOnDblClickCell write FOnDblClickCell;
    property OnClickCell: TGridCellNotifyEvent
      read FOnClickCell write FOnClickCell;
    function GetReadOnly: Boolean; virtual;
    function CanDoSelect(ACol, ARow: Integer): Boolean; dynamic;
    procedure DblClickCell(ACol, ARow: Integer); dynamic;
    procedure ClickCell(ACol, ARow: Integer); dynamic;
    procedure CalcDrawInfo(var DrawInfo: TDzGridDrawInfo);
    procedure CalcFixedAxis(var Axis: TDzGridAxisDrawInfo;
      LineOptions: TGridOptions; FixedCount, FirstCell, CellCount: Integer;
      LineWidth: Integer; GetExtentFunc: TGetExtentsFunc);
    procedure CalcFixedInfo(var DrawInfo: TDzGridDrawInfo);
    procedure CalcSizingState(X, Y: Integer; var State: TGridState;
      var Index: Longint; var SizingPos, SizingOfs: Integer;
      var FixedInfo: TDzGridDrawInfo); virtual;
    procedure ChangeGridOrientation(RightToLeftOrientation: Boolean);
    function CreateEditor: TDzInplaceEdit; virtual;
    procedure DoContextPopup(MousePos: TPoint; var Handled: Boolean); override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyPress(var Key: Char); override;
    procedure MouseDown(Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure AdjustSize(Index, Amount: Longint;
      Rows: Boolean); reintroduce; dynamic;
    function BoxRect(ALeft, ATop, ARight, ABottom: Longint): TRect;
    procedure DoExit; override;
    function CellRect(ACol, ARow: Longint): TRect;
    function CanEditAcceptKey(Key: Char): Boolean; dynamic;
    function CanGridAcceptKey(Key: Word; Shift: TShiftState): Boolean; dynamic;
    function CanEditShow: Boolean; virtual;
    function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
      MousePos: TPoint): Boolean; override;
    procedure FocusCell(ACol, ARow: Longint; MoveAnchor: Boolean);
    function GetEditText(ACol, ARow: Longint): string; dynamic;
    procedure SetEditText(ACol, ARow: Longint; const Value: string); dynamic;
    function GetEditLimit: Integer; dynamic;
    function GetEditMask(ACol, ARow: Longint): string; dynamic;
    function GetEditStyle(ACol, ARow: Longint): TEditStyle; dynamic;
    function GetGridWidth: Integer;
    function GetGridHeight: Integer;
    procedure HideEdit;
    procedure HideEditor;
    procedure ShowEditor;
    procedure ShowEditorChar(Ch: Char);
    procedure InvalidateEditor;
    procedure InvalidateGrid;
    procedure MoveColumn(FromIndex, ToIndex: Longint);
    procedure SelectionChanged; dynamic;
    procedure ColumnMoved(FromIndex, ToIndex: Longint); dynamic;
    procedure MoveRow(FromIndex, ToIndex: Longint);
    procedure RowMoved(FromIndex, ToIndex: Longint); dynamic;
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
      AState: TGridDrawState); virtual; abstract;
    function CellBkColor(ACol, ARow: LongInt;
      const DrawState: TGridDrawState): TColor; dynamic;
    procedure DefineProperties(Filer: TFiler); override;
    procedure MoveColRow(ACol, ARow: Longint; MoveAnchor, Show: Boolean);
    function SelectCell(ACol, ARow: Longint): Boolean; virtual;
    procedure SizeChanged(OldColCount, OldRowCount: Longint); dynamic;
    function Sizing(X, Y: Integer): Boolean;
    procedure ScrollData(DX, DY: Integer);
    procedure TopLeftChanged; dynamic;
    procedure TimedScroll(Direction: TGridScrollDirection); dynamic;
    procedure Paint; override;
    procedure ColWidthsChanged(Index: Integer); dynamic;
    procedure RowHeightsChanged; dynamic;
    procedure DeleteColumn(ACol: Longint); virtual;
    procedure DeleteRow(ARow: Longint); virtual;
    procedure UpdateDesigner;
    function BeginColumnDrag(var Origin, Destination: Integer;
      const MousePt: TPoint): Boolean; dynamic;
    function BeginRowDrag(var Origin, Destination: Integer;
      const MousePt: TPoint): Boolean; dynamic;
    function CheckColumnDrag(var Origin, Destination: Integer;
      const MousePt: TPoint): Boolean; dynamic;
    function CheckRowDrag(var Origin, Destination: Integer;
      const MousePt: TPoint): Boolean; dynamic;
    function EndColumnDrag(var Origin, Destination: Integer;
      const MousePt: TPoint): Boolean; dynamic;
    function EndRowDrag(var Origin, Destination: Integer;
      const MousePt: TPoint): Boolean; dynamic;
    property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle
      default bsSingle;
    property Col: Longint read FCurrent.X write SetCol;
    property Color default clWindow;
    property ColCount: Longint read FColCount write SetColCount default 5;
    property ColWidths[Index: Longint]: Integer
    read GetColWidths write SetColWidths;
    property DefaultColWidth: Integer read FDefaultColWidth write
      SetDefaultColWidth default 64;
    property DefaultDrawing: Boolean read FDefaultDrawing write FDefaultDrawing
      default True;
    property FocusRect: Boolean read FFocusRect write FFocusRect default True;
    property DefaultRowHeight: Integer read FDefaultRowHeight write
      SetDefaultRowHeight default 24;
    property EditorMode: Boolean read FEditorMode write SetEditorMode;
    property FixedColor: TColor read FFixedColor write SetFixedColor default
      clBtnFace;
    property FixedCols: Integer read FFixedCols write SetFixedCols default 1;
    property FixedRows: Integer read FFixedRows write SetFixedRows default 1;
    property GridHeight: Integer read GetGridHeight;
    property HorzLineWidth: Integer read FHorzLineWidth write SetHorzLineWidth
      default 1;
    property VertLineWidth: Integer read FVertLineWidth write SetVertLineWidth
      default 1;
    property GridWidth: Integer read GetGridWidth;
    property HitTest: TPoint read FHitTest;
    property InplaceEditor: TDzInplaceEdit read FInplaceEdit;
    property LeftCol: Longint read FTopLeft.X write SetLeftCol;
    property Options: TGridOptions read FOptions write SetOptions
      default [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
      goRangeSelect];
    property ParentColor default False;
    property Row: Longint read FCurrent.Y write SetRow;
    property RowCount: Longint read FRowCount write SetRowCount default 5;
    property RowHeights[Index: Longint]: Integer read GetRowHeights write
    SetRowHeights;
    property ScrollBars: TScrollStyle read FScrollBars write SetScrollBars
      default ssBoth;
    property Selection: TGridRect read GetSelection write SetSelection;
    property TabStops[Index: Longint]: Boolean
    read GetTabStops write SetTabStops;
    property TopRow: Longint read FTopLeft.Y write SetTopRow;
    property VisibleColCount: Integer read GetVisibleColCount;
    property VisibleRowCount: Integer read GetVisibleRowCount;
    property SelRowCount: Integer read GetSelRowCount;
    property SelRow[index: Integer]: Integer read GetSelRow;
    property HighLightColor: TColor read FHighLightColor write SetHighLightColor;
    property HighLightTextColor: TColor read FHighLightTextColor
      write SetHightLightTextColor;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function MouseCoord(X, Y: Integer): TGridCoord;
    procedure InvalidateCell(ACol, ARow: Longint);
    procedure InvalidateCol(ACol: Longint);
    procedure InvalidateRow(ARow: Longint);
    function CellSelected(ACol, ARow: LongInt): Boolean;
    procedure ClearSelected;
    procedure ToggerSelectedState(ACol, ARow: LongInt);
    procedure SetGridScale(ccnt, rcnt: Integer);
    procedure SelectAll;
    function AllSelected: Boolean;
    property ReadOnly: Boolean read GetReadOnly;
  published
    property TabStop default True;
  end;

  TAskColumnClassEvent = procedure(Sender: TObject;
    var Event: TDzGridColumnClass) of object;
  TClickGridColumnEvent = procedure(Sender: TObject;
    Column: TDzGridColumn) of object;
  TBeforeRowSelectEvent = procedure(Sender: TObject; Column: TDzGridColumn;
    ARow: Integer; var Allow: Boolean) of object;
    
  TDzCustomColumnGrid = class(TDzCustomGrid)
  private
    fColumns: TDzGridColumns;
    fOnCreateColumnClass: TAskColumnClassEvent;
    fOnClickColumn: TClickGridColumnEvent;
    fOnBeforeRowSelect: TBeforeRowSelectEvent;
    procedure UpdateColumn(Index: Integer);
    procedure UpdateColumns;
    procedure SetColumns(const Value: TDzGridColumns);
  protected
    function CanDoSelect(ACol, ARow: Integer): Boolean; override;
    procedure SelectionChanged; override;
    procedure ClickCell(ACol, ARow: Integer); override;
    procedure ColWidthsChanged(Index: Integer); override;
    procedure ColumnMoved(FromIndex, ToIndex: Integer); override;
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
      AState: TGridDrawState); override;
    procedure DrawDataCell(ARow: LongInt; AColumn: TDzGridColumn;
      ARect: TRect; AState: TGridDrawState); virtual; abstract;
    function GetColumnClass: TDzGridColumnClass; dynamic;
    function CreateColumn: TDzGridColumn; dynamic;
    function CreateColumns: TDzGridColumns; dynamic;
    procedure ClickColumn(column: TDzGridColumn); dynamic;
    property OnClickColumn: TClickGridColumnEvent
      read fOnClickColumn write fOnClickColumn;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Columns: TDzGridColumns read fColumns write SetColumns;
    property OnCreateColumnClass: TAskColumnClassEvent
      read fOnCreateColumnClass write fOnCreateColumnClass;
    property OnBeforeRowSelect: TBeforeRowSelectEvent
      read fOnBeforeRowSelect write fOnBeforeRowSelect;
  end;

  TDrawCellEvent = procedure(Sender: TObject; ARow: Longint;
    Column: TDzGridColumn; Rect: TRect; State: TGridDrawState) of object;
  TCellBkColorEvent = procedure(Sender: TObject; ACol, ARow: LongInt;
    const DrawState: TGridDrawState; var AColor: TColor) of object;

  TCustomDrawGrid = class(TDzCustomColumnGrid)
  private
    FOnColumnMoved: TMovedEvent;
    FOnDrawCell: TDrawCellEvent;
    FOnGetEditMask: TGetEditEvent;
    FOnGetEditText: TGetEditEvent;
    FOnRowMoved: TMovedEvent;
    FOnSelectCell: TSelectCellEvent;
    FOnSetEditText: TSetEditEvent;
    FOnTopLeftChanged: TNotifyEvent;
    FOnBkColor: TCellBkColorEvent;
  protected
    procedure ColumnMoved(FromIndex, ToIndex: Longint); override;
    procedure DrawDataCell(ARow: LongInt; AColumn: TDzGridColumn;
      ARect: TRect; AState: TGridDrawState); override;
    function GetEditMask(ACol, ARow: Longint): string; override;
    function GetEditText(ACol, ARow: Longint): string; override;
    procedure RowMoved(FromIndex, ToIndex: Longint); override;
    function SelectCell(ACol, ARow: Longint): Boolean; override;
    procedure SetEditText(ACol, ARow: Longint; const Value: string); override;
    procedure TopLeftChanged; override;
    function CellBkColor(ACol, ARow: LongInt;
      const DrawState: TGridDrawState): TColor; override;
    //procedure WMMouseWheel(var msg: TWMMouseWheel); message WM_MOUSEWHEEL;
    property OnColumnMoved: TMovedEvent read FOnColumnMoved write
      FOnColumnMoved;
    property OnDrawCell: TDrawCellEvent read FOnDrawCell write FOnDrawCell;
    property OnGetEditMask: TGetEditEvent read FOnGetEditMask write
      FOnGetEditMask;
    property OnGetEditText: TGetEditEvent read FOnGetEditText write
      FOnGetEditText;
    property OnRowMoved: TMovedEvent read FOnRowMoved write FOnRowMoved;
    property OnSelectCell: TSelectCellEvent read FOnSelectCell write
      FOnSelectCell;
    property OnSetEditText: TSetEditEvent read FOnSetEditText write
      FOnSetEditText;
    property OnTopLeftChanged: TNotifyEvent read FOnTopLeftChanged write
      FOnTopLeftChanged;
    property OnBkColor: TCellBkColorEvent read FOnBkColor write FOnBkColor;
  public
    function CellRect(ACol, ARow: Longint): TRect;
    procedure MouseToCell(X, Y: Integer; var ACol, ARow: Longint);
    property Canvas;
    property Col;
    property ColWidths;
    property EditorMode;
    property GridHeight;
    property GridWidth;
    property LeftCol;
    property Selection;
    property Row;
    property RowHeights;
    property TabStops;
    property TopRow;
    property SelRowCount;
    property SelRow;
  end;

  { TDrawGrid }

  TDrawGrid = class(TCustomDrawGrid)
  published
    property Align;
    property Anchors;
    property BevelEdges;
    property BevelInner;
    property BevelKind;
    property BevelOuter;
    property BevelWidth;
    property BiDiMode;
    property BorderStyle;
    property Color;
    property Constraints;
    property Ctl3D;
    property DefaultColWidth;
    property DefaultRowHeight;
    property DefaultDrawing;
    property FocusRect;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Enabled;
    property FixedColor;
    property HighLightColor;
    property HighLightTextColor;
    property FixedCols;
    property RowCount;
    property Font;
    property HorzLineWidth;
    property VertLineWidth;
    property Options;
    property ParentBiDiMode;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ScrollBars;
    property ShowHint;
    property TabOrder;
    property Visible;
    property VisibleColCount;
    property VisibleRowCount;
    property OnClick;
    property OnColumnMoved;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnDrawCell;
    property OnClickCell;
    property OnClickColumn;
    property OnDblClickCell;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnGetEditMask;
    property OnGetEditText;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseActivate;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnRowMoved;
    property OnSelectCell;
    property OnSetEditText;
    property OnStartDock;
    property OnStartDrag;
    property OnTopLeftChanged;
    property OnBkColor;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('DZGrids', [TDrawGrid]);
end;

type
  PIntArray = ^TIntArray;
  TIntArray = array[0..MaxCustomExtents] of Integer;

function LongMulDiv(Mult1, Mult2, Div1: Longint): Longint; stdcall;
  external 'kernel32.dll' name 'MulDiv';

procedure InvalidOp(const id: string);
begin
  raise EInvalidGridOperation.Create(id);
end;

function GridRect(Coord1, Coord2: TGridCoord): TGridRect;
begin
  with Result do
  begin
    Left := Coord2.X;
    if Coord1.X < Coord2.X then
      Left := Coord1.X;
    Right := Coord1.X;
    if Coord1.X < Coord2.X then
      Right := Coord2.X;
    Top := Coord2.Y;
    if Coord1.Y < Coord2.Y then
      Top := Coord1.Y;
    Bottom := Coord1.Y;
    if Coord1.Y < Coord2.Y then
      Bottom := Coord2.Y;
  end;
end;

function PointInGridRect(Col, Row: Longint; const Rect: TGridRect): Boolean;
begin
  Result := (Col >= Rect.Left) and (Col <= Rect.Right) and (Row >= Rect.Top)
    and (Row <= Rect.Bottom);
end;

type
  TXorRects = array[0..3] of TRect;

procedure XorRects(const R1, R2: TRect; var XorRects: TXorRects);
var
  Intersect, Union: TRect;

  function PtInRect(X, Y: Integer; const Rect: TRect): Boolean;
  begin
    with Rect do
      Result := (X >= Left) and (X <= Right) and (Y >= Top) and
        (Y <= Bottom);
  end;

  function Includes(const P1: TPoint; var P2: TPoint): Boolean;
  begin
    with P1 do
    begin
      Result := PtInRect(X, Y, R1) or PtInRect(X, Y, R2);
      if Result then
        P2 := P1;
    end;
  end;

  function Build(var R: TRect; const P1, P2, P3: TPoint): Boolean;
  begin
    Build := True;
    with R do
      if Includes(P1, TopLeft) then
      begin
        if not Includes(P3, BottomRight) then
          BottomRight := P2;
      end
      else if Includes(P2, TopLeft) then
        BottomRight := P3
      else
        Build := False;
  end;

begin
  FillChar(XorRects, SizeOf(XorRects), 0);
  if not Bool(IntersectRect(Intersect, R1, R2)) then
  begin
    { Don't intersect so its simple }
    XorRects[0] := R1;
    XorRects[1] := R2;
  end
  else
  begin
    UnionRect(Union, R1, R2);
    if Build(XorRects[0],
      Point(Union.Left, Union.Top),
      Point(Union.Left, Intersect.Top),
      Point(Union.Left, Intersect.Bottom)) then
      XorRects[0].Right := Intersect.Left;
    if Build(XorRects[1],
      Point(Intersect.Left, Union.Top),
      Point(Intersect.Right, Union.Top),
      Point(Union.Right, Union.Top)) then
      XorRects[1].Bottom := Intersect.Top;
    if Build(XorRects[2],
      Point(Union.Right, Intersect.Top),
      Point(Union.Right, Intersect.Bottom),
      Point(Union.Right, Union.Bottom)) then
      XorRects[2].Left := Intersect.Right;
    if Build(XorRects[3],
      Point(Union.Left, Union.Bottom),
      Point(Intersect.Left, Union.Bottom),
      Point(Intersect.Right, Union.Bottom)) then
      XorRects[3].Top := Intersect.Bottom;
  end;
end;

procedure ModifyExtents(var Extents: Pointer; Index, Amount: Longint;
  Default: Integer);
var
  LongSize, OldSize: LongInt;
  NewSize: Integer;
  I: Integer;
begin
  if Amount <> 0 then
  begin
    if not Assigned(Extents) then OldSize := 0
    else OldSize := PIntArray(Extents)^[0];
    if (Index < 0) or (OldSize < Index) then
      InvalidOp(SIndexOutOfRange);
    LongSize := OldSize + Amount;
    if LongSize < 0 then InvalidOp(STooManyDeleted)
    else if LongSize >= MaxListSize - 1 then InvalidOp(SGridTooLarge);
    NewSize := Cardinal(LongSize);
    if NewSize > 0 then Inc(NewSize);
    ReallocMem(Extents, NewSize * SizeOf(Integer));
    if Assigned(Extents) then
    begin
      I := Index + 1;
      while I < NewSize do
      begin
        PIntArray(Extents)^[I] := Default;
        Inc(I);
      end;
      PIntArray(Extents)^[0] := NewSize - 1;
    end;
  end;
end;

procedure UpdateExtents(var Extents: Pointer; NewSize: Longint;
  Default: Integer);
var
  OldSize: Integer;
begin
  if Assigned(Extents) then
    OldSize := PIntArray(Extents)^[0]
  else
    OldSize := 0;
  ModifyExtents(Extents, OldSize, NewSize - OldSize, Default);
end;

procedure MoveExtent(var Extents: Pointer; FromIndex, ToIndex: Longint);
var
  Extent: Integer;
begin
  if Assigned(Extents) then
  begin
    Extent := PIntArray(Extents)^[FromIndex];
    if FromIndex < ToIndex then
      Move(PIntArray(Extents)^[FromIndex + 1], PIntArray(Extents)^[FromIndex],
        (ToIndex - FromIndex) * SizeOf(Integer))
    else if FromIndex > ToIndex then
      Move(PIntArray(Extents)^[ToIndex], PIntArray(Extents)^[ToIndex + 1],
        (FromIndex - ToIndex) * SizeOf(Integer));
    PIntArray(Extents)^[ToIndex] := Extent;
  end;
end;

function CompareExtents(E1, E2: Pointer): Boolean;
var
  I: Integer;
begin
  Result := False;
  if E1 <> nil then
  begin
    if E2 <> nil then
    begin
      for I := 0 to PIntArray(E1)^[0] do
        if PIntArray(E1)^[I] <> PIntArray(E2)^[I] then
          Exit;
      Result := True;
    end
  end
  else
    Result := E2 = nil;
end;

{ TDzInplaceEdit }

constructor TDzInplaceEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ParentCtl3D := False;
  Ctl3D := False;
  TabStop := False;
  BorderStyle := bsNone;
  DoubleBuffered := False;
end;

procedure TDzInplaceEdit.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or ES_MULTILINE;
end;

procedure TDzInplaceEdit.SetGrid(Value: TDzCustomGrid);
begin
  FGrid := Value;
end;

procedure TDzInplaceEdit.CMShowingChanged(var Message: TMessage);
begin
  { Ignore showing using the Visible property }
end;

procedure TDzInplaceEdit.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
  inherited;
  if goTabs in Grid.Options then
    Message.Result := Message.Result or DLGC_WANTTAB;
end;

procedure TDzInplaceEdit.WMPaste(var Message);
begin
  if not EditCanModify then
    Exit;
  inherited
end;

procedure TDzInplaceEdit.WMClear(var Message);
begin
  if not EditCanModify then Exit;
  inherited;
end;

procedure TDzInplaceEdit.WMCut(var Message);
begin
  if not EditCanModify then
    Exit;
  inherited;
end;

procedure TDzInplaceEdit.DblClick;
begin
  inherited;
end;

function TDzInplaceEdit.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
  MousePos: TPoint): Boolean;
begin
  Result := Grid.DoMouseWheel(Shift, WheelDelta, MousePos);
end;

function TDzInplaceEdit.EditCanModify: Boolean;
begin
  Result := Grid.ReadOnly;
end;

procedure TDzInplaceEdit.KeyDown(var Key: Word; Shift: TShiftState);

  procedure SendToParent;
  begin
    Grid.KeyDown(Key, Shift);
    Key := 0;
  end;

  procedure ParentEvent;
  var
    GridKeyDown: TKeyEvent;
  begin
    GridKeyDown := Grid.OnKeyDown;
    if Assigned(GridKeyDown) then
      GridKeyDown(Grid, Key, Shift);
  end;

  function ForwardMovement: Boolean;
  begin
    Result := goAlwaysShowEditor in Grid.Options;
  end;

  function Ctrl: Boolean;
  begin
    Result := ssCtrl in Shift;
  end;

  function Selection: TSelection;
  begin
    SendMessage(Handle, EM_GETSEL, Longint(@Result.StartPos),
      Longint(@Result.EndPos));
  end;

  function CaretPos: Integer;
  var
    P: TPoint;
  begin
    Windows.GetCaretPos(P);
    Result := SendMessage(Handle, EM_CHARFROMPOS, 0, MakeLong(P.X, P.Y));
  end;

  function RightSide: Boolean;
  begin
    with Selection do
      Result := (CaretPos = GetTextLen) and
        ((StartPos = 0) or (EndPos = StartPos)) and (EndPos = GetTextLen);
  end;

  function LeftSide: Boolean;
  begin
    with Selection do
      Result := (CaretPos = 0) and (StartPos = 0) and
        ((EndPos = 0) or (EndPos = GetTextLen));
  end;

begin
  case Key of
    VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT, VK_ESCAPE: SendToParent;
    VK_INSERT:
      if Shift = [] then
        SendToParent
      else if (Shift = [ssShift]) and not Grid.ReadOnly then
        Key := 0;
    VK_LEFT: if ForwardMovement and (Ctrl or LeftSide) then
        SendToParent;
    VK_RIGHT: if ForwardMovement and (Ctrl or RightSide) then
        SendToParent;
    VK_HOME: if ForwardMovement and (Ctrl or LeftSide) then
        SendToParent;
    VK_END: if ForwardMovement and (Ctrl or RightSide) then
        SendToParent;
    VK_F2:
      begin
        ParentEvent;
        if Key = VK_F2 then
        begin
          Deselect;
          Exit;
        end;
      end;
    VK_TAB: if not (ssAlt in Shift) then
        SendToParent;
    VK_DELETE:
      if Ctrl then
        SendToParent
      else if not Grid.ReadOnly then
        Key := 0;
  end;
  if Key <> 0 then
  begin
    ParentEvent;
    inherited KeyDown(Key, Shift);
  end;
end;

procedure TDzInplaceEdit.KeyPress(var Key: Char);
var
  Selection: TSelection;
begin
  Grid.KeyPress(Key);
  if (Key in [#32..#255]) and not Grid.CanEditAcceptKey(Key) then
  begin
    Key := #0;
    MessageBeep(0);
  end;
  case Key of
    #9, #27: Key := #0;
    #13:
      begin
        SendMessage(Handle, EM_GETSEL, Longint(@Selection.StartPos),
          Longint(@Selection.EndPos));
        if (Selection.StartPos = 0) and (Selection.EndPos = GetTextLen) then
          Deselect
        else
          SelectAll;
        Key := #0;
      end;
    ^H, ^V, ^X, #32..#255:
      if not Grid.ReadOnly then
        Key := #0;
  end;
  if Key <> #0 then
    inherited KeyPress(Key);
end;

procedure TDzInplaceEdit.KeyUp(var Key: Word; Shift: TShiftState);
begin
  Grid.KeyUp(Key, Shift);
end;

procedure TDzInplaceEdit.WndProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_SETFOCUS:
      begin
        if (GetParentForm(Self) = nil) or
          GetParentForm(Self).SetFocusedControl(Grid) then
          Dispatch(Message);
        Exit;
      end;
    WM_LBUTTONDOWN:
      begin
        if UINT(GetMessageTime - FClickTime) < GetDoubleClickTime then
          Message.Msg := WM_LBUTTONDBLCLK;
        FClickTime := 0;
      end;
  end;
  inherited WndProc(Message);
end;

procedure TDzInplaceEdit.Deselect;
begin
  SendMessage(Handle, EM_SETSEL, $7FFFFFFF, Longint($FFFFFFFF));
end;

procedure TDzInplaceEdit.Invalidate;
var
  Cur: TRect;
begin
  ValidateRect(Handle, nil);
  InvalidateRect(Handle, nil, True);
  Windows.GetClientRect(Handle, Cur);
  MapWindowPoints(Handle, Grid.Handle, Cur, 2);
  ValidateRect(Grid.Handle, @Cur);
  InvalidateRect(Grid.Handle, @Cur, False);
end;

procedure TDzInplaceEdit.Hide;
begin
  if HandleAllocated and IsWindowVisible(Handle) then
  begin
    Invalidate;
    SetWindowPos(Handle, 0, 0, 0, 0, 0, SWP_HIDEWINDOW or SWP_NOZORDER or
      SWP_NOREDRAW);
    if Focused then
      Windows.SetFocus(Grid.Handle);
  end;
end;

function TDzInplaceEdit.PosEqual(const Rect: TRect): Boolean;
var
  Cur: TRect;
begin
  GetWindowRect(Handle, Cur);
  MapWindowPoints(HWND_DESKTOP, Grid.Handle, Cur, 2);
  Result := EqualRect(Rect, Cur);
end;

procedure TDzInplaceEdit.InternalMove(const Loc: TRect; Redraw: Boolean);
begin
  if IsRectEmpty(Loc) then
    Hide
  else
  begin
    CreateHandle;
    Redraw := Redraw or not IsWindowVisible(Handle);
    Invalidate;
    with Loc do
      SetWindowPos(Handle, HWND_TOP, Left, Top, Right - Left, Bottom - Top,
        SWP_SHOWWINDOW or SWP_NOREDRAW);
    BoundsChanged;
    if Redraw then
      Invalidate;
    if Grid.Focused then
      Windows.SetFocus(Handle);
  end;
end;

procedure TDzInplaceEdit.BoundsChanged;
var
  R: TRect;
begin
  R := Rect(2, 2, Width - 2, Height);
  SendMessage(Handle, EM_SETRECTNP, 0, LongInt(@R));
  SendMessage(Handle, EM_SCROLLCARET, 0, 0);
end;

procedure TDzInplaceEdit.UpdateLoc(const Loc: TRect);
begin
  InternalMove(Loc, False);
end;

function TDzInplaceEdit.Visible: Boolean;
begin
  Result := IsWindowVisible(Handle);
end;

procedure TDzInplaceEdit.Move(const Loc: TRect);
begin
  InternalMove(Loc, True);
end;

procedure TDzInplaceEdit.SetFocus;
begin
  if IsWindowVisible(Handle) then
    Windows.SetFocus(Handle);
end;

procedure TDzInplaceEdit.UpdateContents;
begin
  Text := '';
  EditMask := Grid.GetEditMask(Grid.Col, Grid.Row);
  Text := Grid.GetEditText(Grid.Col, Grid.Row);
  MaxLength := Grid.GetEditLimit;
end;

{ TDzCustomGrid }

constructor TDzCustomGrid.Create(AOwner: TComponent);
const
  GridStyle = [csOpaque, csDoubleClicks, csNeedsBorderPaint,
    csPannable, csCaptureMouse];
begin
  inherited Create(AOwner);
  FSelRows := TDzIntRangeList.Create;
  if NewStyleControls then ControlStyle := GridStyle
  else ControlStyle := GridStyle + [csFramed];
  FReadOnly := True;
  FColCount := 5;
  FRowCount := 5;
  FFixedCols := 1;
  FFixedRows := 1;
  FHorzLineWidth := 1;
  FVertLineWidth := 1;
  FOptions := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
    goRangeSelect];
  DesignOptionsBoost := [goColSizing, goRowSizing];
  FFixedColor := clBtnFace;
  FScrollBars := ssBoth;
  FBorderStyle := bsSingle;
  FDefaultColWidth := 64;
  FDefaultRowHeight := 24;
  FDefaultDrawing := True;
  FFocusRect := True;
  FSaveCellExtents := True;
  FEditorMode := False;
  Color := clWindow;
  FHighLightColor := clHighlight;
  FHighLightTextColor := clHighlightText;
  ParentColor := False;
  TabStop := True;
  SetBounds(Left, Top, FColCount * FDefaultColWidth,
    FRowCount * FDefaultRowHeight);
  Initialize;
end;

destructor TDzCustomGrid.Destroy;
begin
  FInplaceEdit.Free;
  FInplaceEdit := nil;
  inherited Destroy;
  FreeMem(FColWidths);
  FreeMem(FRowHeights);
  FreeMem(FTabStops);
  FSelRows.Free;
end;

procedure TDzCustomGrid.AdjustSize(Index, Amount: Longint; Rows: Boolean);
var
  NewCur: TGridCoord;
  OldRows, OldCols: Longint;
  MovementX, MovementY: Longint;
  MoveRect: TGridRect;
  ScrollArea: TRect;
  AbsAmount: Longint;

  function DoSizeAdjust(var Count: Longint; var Extents: Pointer;
    DefaultExtent: Integer; var Current: Longint): Longint;
  var
    I: Integer;
    NewCount: Longint;
  begin
    NewCount := Count + Amount;
    if (NewCount < Index) then InvalidOp(STooManyDeleted);
    if (Amount < 0) and Assigned(Extents) then
    begin
      Result := 0;
      for I := Index to Index - Amount - 1 do
        Inc(Result, PIntArray(Extents)^[I]);
    end
    else
      Result := Amount * DefaultExtent;
    if Extents <> nil then
      ModifyExtents(Extents, Index, Amount, DefaultExtent);
    Count := NewCount;
    if Current >= Index then
      if (Amount < 0) and (Current < Index - Amount) then
        Current := Index
      else
        Inc(Current, Amount);
  end;

begin
  if Amount = 0 then Exit;
  NewCur := FCurrent;
  OldCols := ColCount;
  OldRows := RowCount;
  MoveRect.Left := FixedCols;
  MoveRect.Right := ColCount - 1;
  MoveRect.Top := FixedRows;
  MoveRect.Bottom := RowCount - 1;
  MovementX := 0;
  MovementY := 0;
  AbsAmount := Amount;
  if (AbsAmount < 0) then AbsAmount := -AbsAmount;
  if Rows then
  begin
    MovementY := DoSizeAdjust(FRowCount, FRowHeights,
      DefaultRowHeight, NewCur.Y);
    MoveRect.Top := Index;
    if Index + AbsAmount <= TopRow then
      MoveRect.Bottom := TopRow - 1;
  end
  else begin
    MovementX := DoSizeAdjust(FColCount, FColWidths, DefaultColWidth, NewCur.X);
    MoveRect.Left := Index;
    if Index + AbsAmount <= LeftCol then
      MoveRect.Right := LeftCol - 1;
  end;
  GridRectToScreenRect(MoveRect, ScrollArea, True);
  if not IsRectEmpty(ScrollArea) then
  begin
    ScrollWindow(Handle, MovementX, MovementY, @ScrollArea, @ScrollArea);
    UpdateWindow(Handle);
  end;
  SizeChanged(OldCols, OldRows);
  if (NewCur.X <> FCurrent.X) or (NewCur.Y <> FCurrent.Y) then
    MoveCurrent(NewCur.X, NewCur.Y, True, True);
end;

function TDzCustomGrid.BoxRect(ALeft, ATop, ARight, ABottom: Longint): TRect;
var
  GridRect: TGridRect;
begin
  GridRect.Left := ALeft;
  GridRect.Right := ARight;
  GridRect.Top := ATop;
  GridRect.Bottom := ABottom;
  GridRectToScreenRect(GridRect, Result, False);
end;

procedure TDzCustomGrid.DoContextPopup(MousePos: TPoint; var Handled: Boolean);
var
  Coord: TGridCoord;
begin
  Coord := Self.MouseCoord(MousePos.X, MousePos.Y);
  if (Coord.X < Self.FixedCols) or (Coord.Y < Self.FixedRows) then
  begin
    Handled := True;
    Exit;
  end;
  if GetKeyState(VK_CONTROL) >= 0 then
  begin
    if not CellSelected(Coord.X, Coord.Y) then
    begin
      FSelRows.Clear;
      FSelRows.Add(Coord.Y);
      Invalidate;
    end;
  end;
end;

procedure TDzCustomGrid.DoExit;
begin
  inherited DoExit;
  if not (goAlwaysShowEditor in Options) then
    HideEditor;
end;

function TDzCustomGrid.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
  MousePos: TPoint): Boolean;
var
  r: Integer;
  mtl: Integer;
begin
  Result := False;
  if Assigned(OnMouseWheel) then
    OnMouseWheel(Self, Shift, WheelDelta, MousePos, Result);
  if not Result then
  begin
    r := Row - WheelDelta div WHEEL_DELTA;
    if (r < FixedRows) then
      r := FixedRows
    else if (r >= RowCount) then
      r := RowCount - 1;
    r := TopRow + r - Row;
    mtl := FMaxTopLeft.Y;
    if (r > mtl) then r := mtl;
    if (r < FixedRows) then r := FixedRows;
    TopRow := r;
    Result := True;
  end;
end;

procedure TDzCustomGrid.DoSelect(RowIndex: Integer; Shift: TShiftState);
var
  Coord: TGridCoord;
begin
  if (RowIndex < FixedRows) or (RowIndex >= RowCount) then Exit;
  if not (goRangeSelect in Self.Options) then Exit;
  FGridState := gsSelecting;
  Coord.X := FixedCols;
  Coord.Y := RowIndex;
  if ssCtrl in Shift then ToggerSelectedState(FixedCols, RowIndex)
  else if ssShift in Shift then
  begin
    FSelRows.Clear;
    MoveAnchor(Coord);
    if (fAnchor.Y > FCurrent.Y) then
      FSelRows.AddRange(FCurrent.Y, fAnchor.Y)
    else
      FSelRows.AddRange(fAnchor.Y, FCurrent.Y);
  end
  else begin
    FSelRows.Clear;
    FSelRows.Add(RowIndex);
  end;
  if not (ssShift in Shift) then MoveCurrent(FixedCols, RowIndex, True, True);
  InvalidateGrid;
end;

function TDzCustomGrid.CellBkColor(ACol, ARow: Integer;
  const DrawState: TGridDrawState): TColor;
begin
  if (gdSelected in DrawState) and not (gdFixed in DrawState) then
    Result := Self.HighLightColor
  else if gdFixed in DrawState then
    Result := Self.FixedColor
  else Result := Self.Color;
end;

function TDzCustomGrid.CellRect(ACol, ARow: Longint): TRect;
begin
  Result := BoxRect(ACol, ARow, ACol, ARow);
end;

function TDzCustomGrid.CellSelected(ACol, ARow: Integer): Boolean;
begin
  if (goRowSelect in Options) then
    Result := FSelRows.Exists(ARow, nil)
  else
    Result := PointInGridRect(ACol, ARow, Selection);
end;

function TDzCustomGrid.CanEditAcceptKey(Key: Char): Boolean;
begin
  Result := True;
end;

function TDzCustomGrid.CanGridAcceptKey(Key: Word; Shift: TShiftState): Boolean;
begin
  Result := True;
end;

function TDzCustomGrid.GetReadOnly: Boolean;
begin
  Result := FReadOnly;
end;

function TDzCustomGrid.CanEditShow: Boolean;
begin
  Result := ([goRowSelect, goEditing] * Options = [goEditing]) and
    FEditorMode and not (csDesigning in ComponentState) and HandleAllocated and
    ((goAlwaysShowEditor in Options) or IsActiveControl);
end;

function TDzCustomGrid.IsActiveControl: Boolean;
var
  H: Hwnd;
  ParentForm: TCustomForm;
begin
  Result := False;
  ParentForm := GetParentForm(Self);
  if Assigned(ParentForm) then
  begin
    if (ParentForm.ActiveControl = Self) then
      Result := True
  end
  else
  begin
    H := GetFocus;
    while IsWindow(H) and (Result = False) do
    begin
      if H = WindowHandle then
        Result := True
      else
        H := GetParent(H);
    end;
  end;
end;

function TDzCustomGrid.GetEditMask(ACol, ARow: Longint): string;
begin
  Result := '';
end;

function TDzCustomGrid.GetEditText(ACol, ARow: Longint): string;
begin
  Result := '';
end;

procedure TDzCustomGrid.SetEditText(ACol, ARow: Longint; const Value: string);
begin
end;

function TDzCustomGrid.GetEditLimit: Integer;
begin
  Result := 0;
end;

function TDzCustomGrid.GetEditStyle(ACol, ARow: Longint): TEditStyle;
begin
  Result := esSimple;
end;

procedure TDzCustomGrid.HideEditor;
begin
  FEditorMode := False;
  HideEdit;
end;

procedure TDzCustomGrid.ShowEditor;
begin
  FEditorMode := True;
  UpdateEdit;
end;

procedure TDzCustomGrid.ShowEditorChar(Ch: Char);
begin
  ShowEditor;
  if FInplaceEdit <> nil then
    PostMessage(FInplaceEdit.Handle, WM_CHAR, Word(Ch), 0);
end;

procedure TDzCustomGrid.InvalidateEditor;
begin
  FInplaceCol := -1;
  FInplaceRow := -1;
  UpdateEdit;
end;

procedure TDzCustomGrid.ReadColWidths(Reader: TReader);
var
  I: Integer;
begin
  with Reader do
  begin
    ReadListBegin;
    for I := 0 to ColCount - 1 do
      ColWidths[I] := ReadInteger;
    ReadListEnd;
  end;
end;

procedure TDzCustomGrid.ReadRowHeights(Reader: TReader);
var
  I: Integer;
begin
  with Reader do
  begin
    ReadListBegin;
    for I := 0 to RowCount - 1 do
      RowHeights[I] := ReadInteger;
    ReadListEnd;
  end;
end;

procedure TDzCustomGrid.WriteColWidths(Writer: TWriter);
var
  I: Integer;
begin
  with Writer do
  begin
    WriteListBegin;
    for I := 0 to ColCount - 1 do
      WriteInteger(ColWidths[I]);
    WriteListEnd;
  end;
end;

procedure TDzCustomGrid.WriteRowHeights(Writer: TWriter);
var
  I: Integer;
begin
  with Writer do
  begin
    WriteListBegin;
    for I := 0 to RowCount - 1 do
      WriteInteger(RowHeights[I]);
    WriteListEnd;
  end;
end;

procedure TDzCustomGrid.DblClickCell(ACol, ARow: Integer);
begin
  if Assigned(FOnDblClickCell) then
    FOnDblClickCell(Self, ACol, ARow);
end;

procedure TDzCustomGrid.DefineProperties(Filer: TFiler);

  function DoColWidths: Boolean;
  begin
    if Filer.Ancestor <> nil then
      Result := not CompareExtents(TDzCustomGrid(Filer.Ancestor).FColWidths,
        FColWidths)
    else
      Result := FColWidths <> nil;
  end;

  function DoRowHeights: Boolean;
  begin
    if Filer.Ancestor <> nil then
      Result := not CompareExtents(TDzCustomGrid(Filer.Ancestor).FRowHeights,
        FRowHeights)
    else
      Result := FRowHeights <> nil;
  end;

begin
  inherited DefineProperties(Filer);
  if FSaveCellExtents then
    with Filer do
    begin
      DefineProperty('ColWidths', ReadColWidths, WriteColWidths, DoColWidths);
      DefineProperty('RowHeights', ReadRowHeights, WriteRowHeights,
        DoRowHeights);
    end;
end;

procedure TDzCustomGrid.MoveColumn(FromIndex, ToIndex: Longint);
var
  Rect: TGridRect;
begin
  if (FromIndex = ToIndex) then Exit;
  if Assigned(FColWidths) then
  begin
    MoveExtent(FColWidths, FromIndex + 1, ToIndex + 1);
    MoveExtent(FTabStops, FromIndex + 1, ToIndex + 1);
  end;
  MoveAdjust(FCurrent.X, FromIndex, ToIndex);
  MoveAdjust(fAnchor.X, FromIndex, ToIndex);
  MoveAdjust(FInplaceCol, FromIndex, ToIndex);
  UpdateMaxTopLeft;
  Rect.Top := 0;
  Rect.Bottom := TopRow + VisibleRowCount;
  if FromIndex < ToIndex then
  begin
    Rect.Left := FromIndex;
    Rect.Right := ToIndex;
  end
  else begin
    Rect.Left := ToIndex;
    Rect.Right := FromIndex;
  end;
  InvalidateRect(Rect);
  ColumnMoved(FromIndex, ToIndex);
  UpdateEdit;
end;

procedure TDzCustomGrid.ColumnMoved(FromIndex, ToIndex: Longint);
begin

end;

procedure TDzCustomGrid.MoveRow(FromIndex, ToIndex: Longint);
begin
  if Assigned(FRowHeights) then
    MoveExtent(FRowHeights, FromIndex + 1, ToIndex + 1);
  MoveAdjust(FCurrent.Y, FromIndex, ToIndex);
  MoveAdjust(fAnchor.Y, FromIndex, ToIndex);
  MoveAdjust(FInplaceRow, FromIndex, ToIndex);
  RowMoved(FromIndex, ToIndex);
  if Assigned(FRowHeights) then
  begin
    UpdateMaxTopLeft;
    RowHeightsChanged;
  end
  else UpdateEdit;
end;

procedure TDzCustomGrid.RowMoved(FromIndex, ToIndex: Longint);
begin
end;

function TDzCustomGrid.MouseCoord(X, Y: Integer): TGridCoord;
begin
  Result := CalcCoordFromPoint(X, Y, FCurrentDrawInfo);
  if Result.X < 0 then
    Result.Y := -1
  else if Result.Y < 0 then
    Result.X := -1;
end;

procedure TDzCustomGrid.MoveColRow(ACol, ARow: Longint; MoveAnchor,
  Show: Boolean);
begin
  MoveCurrent(ACol, ARow, MoveAnchor, Show);
end;

procedure TDzCustomGrid.SelectAll;
begin
  FSelRows.Clear;
  FSelRows.AddRange(FixedRows, RowCount - 1);
  InvalidateGrid;
end;

function TDzCustomGrid.SelectCell(ACol, ARow: Longint): Boolean;
begin
  Result := True;
end;

procedure TDzCustomGrid.SizeChanged(OldColCount, OldRowCount: Longint);
begin
end;

function TDzCustomGrid.Sizing(X, Y: Integer): Boolean;
var
  State: TGridState;
  Index: Longint;
  Pos, Ofs: Integer;
begin
  State := FGridState;
  if (State = gsNormal) then
    CalcSizingState(X, Y, State, Index, Pos, Ofs, FCurrentDrawInfo);
  Result := State <> gsNormal;
end;

procedure TDzCustomGrid.ToggerSelectedState(ACol, ARow: Integer);
begin
  if goRowSelect in Options then
  begin
    if (FSelRows.Exists(ARow, nil)) then FSelRows.Remove(ARow)
    else FSelRows.Add(ARow);
  end;
  SelectionChanged;
end;

procedure TDzCustomGrid.TopLeftChanged;
begin
  if FEditorMode and (FInplaceEdit <> nil) then
    FInplaceEdit.UpdateLoc(CellRect(Col, Row));
end;

procedure FillDWord(var Dest; Count, Value: Integer); register;
asm
  XCHG  EDX, ECX
  PUSH  EDI
  MOV   EDI, EAX
  MOV   EAX, EDX
  REP   STOSD
  POP   EDI
end;

{ StackAlloc allocates a 'small' block of memory from the stack by
  decrementing SP.  This provides the allocation speed of a local variable,
  but the runtime size flexibility of heap allocated memory.  }

function StackAlloc(Size: Integer): Pointer; register;
asm
  POP   ECX          { return address }
  MOV   EDX, ESP
  ADD   EAX, 3
  AND   EAX, not 3   // round up to keep ESP dword aligned
  CMP   EAX, 4092
  JLE   @@2
@@1:
  SUB   ESP, 4092
  PUSH  EAX          { make sure we touch guard page, to grow stack }
  SUB   EAX, 4096
  JNS   @@1
  ADD   EAX, 4096
@@2:
  SUB   ESP, EAX
  MOV   EAX, ESP     { function result = low memory address of block }
  PUSH  EDX          { save original SP, for cleanup }
  MOV   EDX, ESP
  SUB   EDX, 4
  PUSH  EDX          { save current SP, for sanity check  (sp = [sp]) }
  PUSH  ECX          { return to caller }
end;

{ StackFree pops the memory allocated by StackAlloc off the stack.
- Calling StackFree is optional - SP will be restored when the calling routine
  exits, but it's a good idea to free the stack allocated memory ASAP anyway.
- StackFree must be called in the same stack context as StackAlloc - not in
  a subroutine or finally block.
- Multiple StackFree calls must occur in reverse order of their corresponding
  StackAlloc calls.
- Built-in sanity checks guarantee that an improper call to StackFree will not
  corrupt the stack. Worst case is that the stack block is not released until
  the calling routine exits. }

procedure StackFree(P: Pointer); register;
asm
  POP   ECX                     { return address }
  MOV   EDX, DWORD PTR [ESP]
  SUB   EAX, 8
  CMP   EDX, ESP                { sanity check #1 (SP = [SP]) }
  JNE   @@1
  CMP   EDX, EAX                { sanity check #2 (P = this stack block) }
  JNE   @@1
  MOV   ESP, DWORD PTR [ESP+4]  { restore previous SP  }
@@1:
  PUSH  ECX                     { return to caller }
end;

procedure TDzCustomGrid.Paint;
var
  LineColor: TColor;
  Sel: TGridRect;
  UpdateRect: TRect;
  AFocRect, FocRect: TRect;
  PointsList: PIntArray;
  StrokeList: PIntArray;
  MaxStroke: Integer;

  procedure DrawLines(DoHorz, DoVert: Boolean; Col, Row: Longint;
    const CellBounds: array of Integer; OnColor, OffColor: TColor);

    { Cellbounds is 4 integers: StartX, StartY, StopX, StopY
      Horizontal lines:  MajorIndex = 0
      Vertical lines:    MajorIndex = 1 }

  const
    FlatPenStyle = PS_Geometric or PS_Solid or PS_EndCap_Flat or PS_Join_Miter;

    procedure DrawAxisLines(const AxisInfo: TDzGridAxisDrawInfo;
      Cell, MajorIndex: Integer; UseOnColor: Boolean);
    var
      Line: Integer;
      LogBrush: TLOGBRUSH;
      Index: Integer;
      Points: PIntArray;
      StopMajor, StartMinor, StopMinor, StopIndex: Integer;
      LineIncr: Integer;
    begin
      with Canvas, AxisInfo do
      begin
        if EffectiveLineWidth <> 0 then
        begin
          Pen.Width := EffectiveLineWidth;
          if UseOnColor then
            Pen.Color := OnColor
          else
            Pen.Color := OffColor;
          if Pen.Width > 1 then
          begin
            LogBrush.lbStyle := BS_Solid;
            LogBrush.lbColor := Pen.Color;
            LogBrush.lbHatch := 0;
            Pen.Handle := ExtCreatePen(FlatPenStyle, Pen.Width, LogBrush, 0,
              nil);
          end;
          Points := PointsList;
          Line := CellBounds[MajorIndex] + EffectiveLineWidth shr 1 +
            GetExtent(Cell);
          //!!! ??? Line needs to be incremented for RightToLeftAlignment ???
          if UseRightToLeftAlignment and (MajorIndex = 0) then Inc(Line);
          StartMinor := CellBounds[MajorIndex xor 1];
          StopMinor := CellBounds[2 + (MajorIndex xor 1)];
          StopMajor := CellBounds[2 + MajorIndex] + EffectiveLineWidth;
          StopIndex := MaxStroke * 4;
          Index := 0;
          repeat
            Points^[Index + MajorIndex] := Line; { MoveTo }
            Points^[Index + (MajorIndex xor 1)] := StartMinor;
            Inc(Index, 2);
            Points^[Index + MajorIndex] := Line; { LineTo }
            Points^[Index + (MajorIndex xor 1)] := StopMinor;
            Inc(Index, 2);
            // Skip hidden columns/rows.  We don't have stroke slots for them
            // A column/row with an extent of -EffectiveLineWidth is hidden
            repeat
              Inc(Cell);
              LineIncr := GetExtent(Cell) + EffectiveLineWidth;
            until (LineIncr > 0) or (Cell > LastFullVisibleCell);
            Inc(Line, LineIncr);
          until (Line > StopMajor) or (Cell > LastFullVisibleCell) or (Index >
            StopIndex);
          { 2 integers per point, 2 points per line -> Index div 4 }
          PolyPolyLine(Canvas.Handle, Points^, StrokeList^, Index shr 2);
        end;
      end;
    end;

  begin
    if (CellBounds[0] = CellBounds[2]) or
      (CellBounds[1] = CellBounds[3]) then Exit;
    with FCurrentDrawInfo do
      if not DoHorz then
      begin
        DrawAxisLines(Vert, Row, 1, DoHorz);
        DrawAxisLines(Horz, Col, 0, DoVert);
      end
      else begin DrawAxisLines(Horz, Col, 0, DoVert);
        DrawAxisLines(Vert, Row, 1, DoHorz);
      end;
  end;

  procedure DrawCells(ACol, ARow: Longint;
    StartX, StartY, StopX, StopY: Integer;
    Color: TColor; IncludeDrawState: TGridDrawState);
  var
    CurCol, CurRow: Longint;
    AWhere, Where: TRect;
    DrawState: TGridDrawState;
    Focused: Boolean;
  begin
    CurRow := ARow;
    Where.Top := StartY;
    with FCurrentDrawInfo do
      while (Where.Top < StopY) and (CurRow < RowCount) do
      begin
        CurCol := ACol;
        Where.Left := StartX;
        Where.Bottom := Where.Top + RowHeights[CurRow];
        while (Where.Left < StopX) and (CurCol < ColCount) do
        begin
          Where.Right := Where.Left + ColWidths[CurCol];
          if ((Where.Right > Where.Left) and
            RectVisible(Canvas.Handle, Where)) then
          begin
            DrawState := IncludeDrawState;
            Focused := IsActiveControl;
            if Focused and (CurRow = Row) and (CurCol = Col) then
            begin
              SetCaretPos(Where.Left, Where.Top);
              Include(DrawState, gdFocused);
            end;
            if CellSelected(CurCol, CurRow) then
              Include(DrawState, gdSelected);
            if not (gdFocused in DrawState) or not (goEditing in Options) or
              not FEditorMode or (csDesigning in ComponentState) then
            begin
              if DefaultDrawing or (csDesigning in ComponentState) then
              begin
                with Canvas do
                begin
                  Font := Self.Font;
                  Brush.Color := CellBkColor(CurCol, CurRow, DrawState);
                  if (gdSelected in DrawState) and not
                    (gdFixed in DrawState) then
                    Font.Color := Self.HighLightTextColor;
                  FillRect(Where);
                end;
              end;
              DrawCell(CurCol, CurRow, Where, DrawState);

              if DefaultDrawing and not (csDesigning in ComponentState) and
                (gdFocused in DrawState) and FocusRect and
                ([goEditing, goAlwaysShowEditor] * Options <>
                [goEditing, goAlwaysShowEditor])
                and not (goRowSelect in Options) then
              begin
                if not UseRightToLeftAlignment then
                  DrawFocusRect(Canvas.Handle, Where)
                else
                begin
                  AWhere := Where;
                  AWhere.Left := Where.Right;
                  AWhere.Right := Where.Left;
                  DrawFocusRect(Canvas.Handle, AWhere);
                end;
              end;
            end;
          end;
          Where.Left := Where.Right + Horz.EffectiveLineWidth;
          Inc(CurCol);
        end;
        Where.Top := Where.Bottom + Vert.EffectiveLineWidth;
        Inc(CurRow);
      end;
  end;

begin
  if UseRightToLeftAlignment then ChangeGridOrientation(True);

  UpdateRect := Canvas.ClipRect;
  with FCurrentDrawInfo do
  begin
    if (Horz.EffectiveLineWidth > 0) or (Vert.EffectiveLineWidth > 0) then
    begin
      { Draw the grid line in the four areas (fixed, fixed), (variable, fixed),
        (fixed, variable) and (variable, variable) }
      LineColor := clSilver;
      MaxStroke := Max(Horz.LastFullVisibleCell - LeftCol + FixedCols,
        Vert.LastFullVisibleCell - TopRow + FixedRows) + 3;
      PointsList := StackAlloc(MaxStroke * sizeof(TPoint) * 2);
      StrokeList := StackAlloc(MaxStroke * sizeof(Integer));
      FillDWord(StrokeList^, MaxStroke, 2);

      if ColorToRGB(Color) = clSilver then LineColor := clGray;
      DrawLines(goFixedHorzLine in Options, goFixedVertLine in Options,
        0, 0, [0, 0, Horz.FixedBoundary, Vert.FixedBoundary], clBlack,
        FixedColor);
      DrawLines(goFixedHorzLine in Options, goFixedVertLine in Options,
        LeftCol, 0, [Horz.FixedBoundary, 0, Horz.GridBoundary,
        Vert.FixedBoundary], clBlack, FixedColor);
      DrawLines(goFixedHorzLine in Options, goFixedVertLine in Options,
        0, TopRow, [0, Vert.FixedBoundary, Horz.FixedBoundary,
        Vert.GridBoundary], clBlack, FixedColor);
      DrawLines(goHorzLine in Options, goVertLine in Options, LeftCol,
        TopRow, [Horz.FixedBoundary, Vert.FixedBoundary, Horz.GridBoundary,
        Vert.GridBoundary], LineColor, Color);

      StackFree(StrokeList);
      StackFree(PointsList);
    end;

    { Draw the cells in the four areas }
    Sel := Selection;

    DrawCells(0, 0, 0, 0, Horz.FixedBoundary, Vert.FixedBoundary, FixedColor,
      [gdFixed]);
    DrawCells(LeftCol, 0, Horz.FixedBoundary - FColOffset, 0, Horz.GridBoundary,
      //!! clip
      Vert.FixedBoundary, FixedColor, [gdFixed]);
    DrawCells(0, TopRow, 0, Vert.FixedBoundary, Horz.FixedBoundary,
      Vert.GridBoundary, FixedColor, [gdFixed]);
    DrawCells(LeftCol, TopRow, Horz.FixedBoundary - FColOffset, //!! clip
      Vert.FixedBoundary, Horz.GridBoundary, Vert.GridBoundary, Color, []);

    if not (csDesigning in ComponentState) and
      (goDrawFocusSelected in Options) and
      (goRowSelect in Options) and DefaultDrawing and Focused then
    begin
      GridRectToScreenRect(GetSelection, FocRect, False);
      if not UseRightToLeftAlignment then
        Canvas.DrawFocusRect(FocRect)
      else
      begin
        AFocRect := FocRect;
        AFocRect.Left := FocRect.Right;
        AFocRect.Right := FocRect.Left;
        DrawFocusRect(Canvas.Handle, AFocRect);
      end;
    end;

    { Fill in area not occupied by cells }
    if Horz.GridBoundary < Horz.GridExtent then
    begin
      Canvas.Brush.Color := Color;
      Canvas.FillRect(Rect(Horz.GridBoundary, 0, Horz.GridExtent,
        Vert.GridBoundary));
    end;
    if Vert.GridBoundary < Vert.GridExtent then
    begin
      Canvas.Brush.Color := Color;
      Canvas.FillRect(Rect(0, Vert.GridBoundary, Horz.GridExtent,
        Vert.GridExtent));
    end;
  end;

  if UseRightToLeftAlignment then
    ChangeGridOrientation(False);
end;

procedure TDzCustomGrid.CalcAxis(var AxisInfo: TDzGridAxisDrawInfo;
  UseExtent: Integer);
var
  I: Integer;
begin
  with AxisInfo do
  begin
    GridExtent := UseExtent;
    GridBoundary := FixedBoundary;
    FullVisBoundary := FixedBoundary;
    LastFullVisibleCell := FirstGridCell;
    for I := FirstGridCell to GridCellCount - 1 do
    begin
      Inc(GridBoundary, GetExtent(I) + EffectiveLineWidth);
      if GridBoundary > GridExtent + EffectiveLineWidth then
      begin
        GridBoundary := GridExtent;
        Break;
      end;
      LastFullVisibleCell := I;
      FullVisBoundary := GridBoundary;
    end;
  end;
end;

function TDzCustomGrid.CalcCoordFromPoint(X, Y: Integer;
  const DrawInfo: TDzGridDrawInfo): TGridCoord;

  function DoCalc(const AxisInfo: TDzGridAxisDrawInfo; N: Integer): Integer;
  var
    I, Start, Stop: Longint;
    Line: Integer;
  begin
    with AxisInfo do
    begin
      if N < FixedBoundary then
      begin
        Start := 0;
        Stop := FixedCellCount - 1;
        Line := 0;
      end
      else begin
        Start := FirstGridCell;
        Stop := GridCellCount - 1;
        Line := FixedBoundary;
      end;
      for I := Start to Stop do
      begin
        Inc(Line, GetExtent(I) + EffectiveLineWidth);
        if N < Line then
        begin
          Result := I;
          Exit;
        end;
      end;
      Result := -1;
    end;
  end;

  function DoCalcRightToLeft(const AxisInfo: TDzGridAxisDrawInfo; N: Integer):
      Integer;
  var
    I, Start, Stop: Longint;
    Line: Integer;
  begin
    N := ClientWidth - N;
    with AxisInfo do
    begin
      if N < FixedBoundary then
      begin
        Start := 0;
        Stop := FixedCellCount - 1;
        Line := ClientWidth;
      end
      else begin
        Start := FirstGridCell;
        Stop := GridCellCount - 1;
        Line := FixedBoundary;
      end;
      Result := -1;
      for I := Start to Stop do
      begin
        Inc(Line, GetExtent(I) + EffectiveLineWidth);
        if N < Line then
        begin
          Result := I;
          Exit;
        end;
      end;
    end;
  end;

begin
  if not UseRightToLeftAlignment then
    Result.X := DoCalc(DrawInfo.Horz, X)
  else
    Result.X := DoCalcRightToLeft(DrawInfo.Horz, X);
  Result.Y := DoCalc(DrawInfo.Vert, Y);
end;

procedure TDzCustomGrid.CalcDrawInfo(var DrawInfo: TDzGridDrawInfo);
var
  rgnWidth, rgnHeight: Integer;
begin
  rgnWidth := ClientWidth;
  rgnHeight := ClientHeight;
  CalcDrawInfoXY(DrawInfo, rgnWidth, rgnHeight);
end;

procedure TDzCustomGrid.CalcDrawInfoXY(var DrawInfo: TDzGridDrawInfo;
  UseWidth, UseHeight: Integer);
begin
  CalcFixedInfo(DrawInfo);
  CalcAxis(DrawInfo.Horz, UseWidth);
  CalcAxis(DrawInfo.Vert, UseHeight);
end;

procedure TDzCustomGrid.CalcFixedAxis(var Axis: TDzGridAxisDrawInfo;
  LineOptions: TGridOptions; FixedCount, FirstCell, CellCount: Integer;
  LineWidth: Integer; GetExtentFunc: TGetExtentsFunc);
var
  I: Integer;
begin
  with Axis do
  begin
    if LineOptions * Self.Options = [] then EffectiveLineWidth := 0
    else EffectiveLineWidth := LineWidth;

    FixedBoundary := 0;
    for I := 0 to FixedCount - 1 do
      Inc(FixedBoundary, GetExtentFunc(I) + EffectiveLineWidth);

    FixedCellCount := FixedCount;
    FirstGridCell := FirstCell;
    GridCellCount := CellCount;
    GetExtent := GetExtentFunc;
  end;
end;

procedure TDzCustomGrid.CalcFixedInfo(var DrawInfo: TDzGridDrawInfo);
begin
  CalcFixedAxis(DrawInfo.Horz, [goFixedVertLine, goVertLine], FixedCols,
    LeftCol, ColCount, VertLineWidth, GetColWidths);
  CalcFixedAxis(DrawInfo.Vert, [goFixedHorzLine, goHorzLine], FixedRows,
    TopRow, RowCount, HorzLineWidth, GetRowHeights);
end;

{ Calculates the TopLeft that will put the given Coord in view }

function TDzCustomGrid.CalcMaxCell(const Axis: TDzGridAxisDrawInfo;
  Start: Integer): Integer;
var
  Line: Integer;
  I, Extent: Longint;
begin
  Result := Start;
  with Axis do
  begin
    Line := GridExtent + EffectiveLineWidth;
    for I := Start downto FixedCellCount do
    begin
      Extent := GetExtent(I);
      if Extent > 0 then
      begin
        Line := Line - Extent - EffectiveLineWidth;
        if Line <= FixedBoundary then
        begin
          if (Result = Start) and (GetExtent(Start) <= 0) then
            Result := I;
          Break;
        end;
        Result := I;
      end;
    end;
  end;
end;

function TDzCustomGrid.CalcMaxTopLeft(const BottomRight: TGridCoord;
  const DrawInfo: TDzGridDrawInfo): TGridCoord;
begin
  Result.X := CalcMaxCell(DrawInfo.Horz, BottomRight.X);
  Result.Y := CalcMaxCell(DrawInfo.Vert, BottomRight.Y);
end;

procedure TDzCustomGrid.CalcSizingState(X, Y: Integer; var State: TGridState;
  var Index: Longint; var SizingPos, SizingOfs: Integer;
  var FixedInfo: TDzGridDrawInfo);

  procedure CalcAxisState(const AxisInfo: TDzGridAxisDrawInfo; Pos: Integer;
    NewState: TGridState);
  var
    I, Line, Back, Range: Integer;
  begin
    if (NewState = gsColSizing) and UseRightToLeftAlignment then
      Pos := ClientWidth - Pos;
    with AxisInfo do
    begin
      Line := FixedBoundary;
      Range := EffectiveLineWidth;
      Back := 0;
      if Range < 7 then
      begin
        Range := 7;
        Back := (Range - EffectiveLineWidth) shr 1;
      end;
      for I := FirstGridCell to GridCellCount - 1 do
      begin
        Inc(Line, GetExtent(I));
        if Line > GridBoundary then
          Break;
        if (Pos >= Line - Back) and (Pos <= Line - Back + Range) then
        begin
          State := NewState;
          SizingPos := Line;
          SizingOfs := Line - Pos;
          Index := I;
          Exit;
        end;
        Inc(Line, EffectiveLineWidth);
      end;
      if (GridBoundary = GridExtent) and (Pos >= GridExtent - Back)
        and (Pos <= GridExtent) then
      begin
        State := NewState;
        SizingPos := GridExtent;
        SizingOfs := GridExtent - Pos;
        Index := LastFullVisibleCell + 1;
      end;
    end;
  end;

  function XOutsideHorzFixedBoundary: Boolean;
  begin
    with FixedInfo do
      if not UseRightToLeftAlignment then
        Result := X > Horz.FixedBoundary
      else
        Result := X < ClientWidth - Horz.FixedBoundary;
  end;

  function XOutsideOrEqualHorzFixedBoundary: Boolean;
  begin
    with FixedInfo do
      if not UseRightToLeftAlignment then
        Result := X >= Horz.FixedBoundary
      else
        Result := X <= ClientWidth - Horz.FixedBoundary;
  end;

var
  EffectiveOptions: TGridOptions;
begin
  State := gsNormal;
  Index := -1;
  EffectiveOptions := Options;
  if csDesigning in ComponentState then
    EffectiveOptions := EffectiveOptions + DesignOptionsBoost;
  if [goColSizing, goRowSizing] * EffectiveOptions <> [] then
  begin
    with FixedInfo do
    begin
      Vert.GridExtent := ClientHeight;
      Horz.GridExtent := ClientWidth;
      if (XOutsideHorzFixedBoundary) and (goColSizing in EffectiveOptions) then
      begin
        if Y >= Vert.FixedBoundary then
          Exit;
        CalcAxisState(Horz, X, gsColSizing);
      end
      else if (Y > Vert.FixedBoundary) and (goRowSizing in EffectiveOptions)
        then
      begin
        if XOutsideOrEqualHorzFixedBoundary then
          Exit;
        CalcAxisState(Vert, Y, gsRowSizing);
      end;
    end;
  end;
end;

procedure TDzCustomGrid.ChangeGridOrientation(RightToLeftOrientation: Boolean);
var
  Org: TPoint;
  Ext: TPoint;
begin
  if RightToLeftOrientation then
  begin
    Org := Point(ClientWidth, 0);
    Ext := Point(-1, 1);
    SetMapMode(Canvas.Handle, mm_Anisotropic);
    SetWindowOrgEx(Canvas.Handle, Org.X, Org.Y, nil);
    SetViewportExtEx(Canvas.Handle, ClientWidth, ClientHeight, nil);
    SetWindowExtEx(Canvas.Handle, Ext.X * ClientWidth, Ext.Y * ClientHeight,
      nil);
  end
  else
  begin
    Org := Point(0, 0);
    Ext := Point(1, 1);
    SetMapMode(Canvas.Handle, MM_ANISOTROPIC);
    SetWindowOrgEx(Canvas.Handle, Org.X, Org.Y, nil);
    SetViewportExtEx(Canvas.Handle, ClientWidth, ClientHeight, nil);
    SetWindowExtEx(Canvas.Handle, Ext.X * ClientWidth,
      Ext.Y * ClientHeight, nil);
  end;
end;

procedure TDzCustomGrid.ChangeSize(NewColCount, NewRowCount: Longint);
var
  OldColCount, OldRowCount: Longint;
  OldDrawInfo: TDzGridDrawInfo;

  procedure MinRedraw(Axis: Integer;
    const OldInfo, NewInfo: TDzGridAxisDrawInfo);
  var
    R: TRect;
    First: Integer;
  begin
    First := Min(OldInfo.LastFullVisibleCell, NewInfo.LastFullVisibleCell);
    //Get the rectangle around the leftmost or topmost cell in the target range.
    R := CellRect(First and not Axis, First and Axis);
    R.Bottom := Height;
    R.Right := Width;
    Windows.InvalidateRect(Handle, @R, False);
  end;

  procedure DoChange;
  var
    Coord: TGridCoord;
  begin
    if FColWidths <> nil then
      UpdateExtents(FColWidths, ColCount, DefaultColWidth);
    if FTabStops <> nil then
      UpdateExtents(FTabStops, ColCount, Integer(True));
    if FRowHeights <> nil then
      UpdateExtents(FRowHeights, RowCount, DefaultRowHeight);
    Coord := FCurrent;
    if Row >= RowCount then Coord.Y := RowCount - 1;
    if Col >= ColCount then Coord.X := ColCount - 1;
    if (FCurrent.X <> Coord.X) or (FCurrent.Y <> Coord.Y) then
      MoveCurrent(Coord.X, Coord.Y, True, True);
    if (fAnchor.X <> Coord.X) or (fAnchor.Y <> Coord.Y) then
      MoveAnchor(Coord);
    if VirtualView or
      (LeftCol <> FCurrentDrawInfo.Horz.FirstGridCell) or
      (TopRow <> FCurrentDrawInfo.Vert.FirstGridCell) then
    begin
      InvalidateGrid;
    end
    else if HandleAllocated then
    begin
      OldDrawInfo := FCurrentDrawInfo;
      UpdateMaxTopLeft;
      MinRedraw(0, OldDrawInfo.Horz, FCurrentDrawInfo.Horz);
      MinRedraw(-1, OldDrawInfo.Vert, FCurrentDrawInfo.Vert);
    end;
    SizeChanged(OldColCount, OldRowCount);
  end;

begin
  OldColCount := FColCount;
  OldRowCount := FRowCount;
  FColCount := NewColCount;
  FRowCount := NewRowCount;
  if FixedCols > NewColCount then FFixedCols := NewColCount - 1;
  if FixedRows > NewRowCount then FFixedRows := NewRowCount - 1;
  try
    DoChange;
  except
    { Could not change size so try to clean up by setting the size back }
    FColCount := OldColCount;
    FRowCount := OldRowCount;
    DoChange;
    InvalidateGrid;
    raise;
  end;
end;

{ Will move TopLeft so that Coord is in view }

procedure TDzCustomGrid.ClampInView(const Coord: TGridCoord);
var
  OldTopLeft, mtl: TGridCoord;
  scrolled: Boolean;
begin
  if HandleAllocated then
    with FCurrentDrawInfo, Coord do
    begin
      scrolled := False;
      mtl.X := -1;
      OldTopLeft := FTopLeft;
      if X < LeftCol then
      begin
        FTopLeft.X := X;
        scrolled := True;
      end
      else if X > Horz.LastFullVisibleCell then
      begin
        mtl := CalcMaxTopLeft(Coord, FCurrentDrawInfo);
        FTopLeft.X := mtl.X;
        scrolled := True;
      end;
      if Y < TopRow then
      begin
        FTopLeft.Y := Y;
        scrolled := True;
      end
      else if Y > Vert.LastFullVisibleCell then
      begin
        if mtl.X < 0 then
          mtl := CalcMaxTopLeft(Coord, FCurrentDrawInfo);
        FTopLeft.Y := mtl.Y;
        scrolled := True;
      end;
      if scrolled then
      begin
        TopLeftMoved(OldTopLeft);
        Update;
      end;
    end;
end;

procedure TDzCustomGrid.ClearSelected;
begin
  fSelRows.Clear;
  InvalidateGrid;
end;

procedure TDzCustomGrid.ClickCell(ACol, ARow: Integer);
begin
  if (Assigned(FOnClickCell)) then
    FOnClickCell(Self, ACol, ARow);
end;

procedure TDzCustomGrid.DrawSizingLine(const DrawInfo: TDzGridDrawInfo);
var
  OldPen: TPen;
begin
  OldPen := TPen.Create;
  try
    with Canvas, DrawInfo do
    begin
      OldPen.Assign(Pen);
      Pen.Style := psDot;
      Pen.Mode := pmXor;
      Pen.Width := 1;
      try
        if FGridState = gsRowSizing then
        begin
          if UseRightToLeftAlignment then
          begin
            MoveTo(Horz.GridExtent, FSizingPos);
            LineTo(Horz.GridExtent - Horz.GridBoundary, FSizingPos);
          end
          else
          begin
            MoveTo(0, FSizingPos);
            LineTo(Horz.GridBoundary, FSizingPos);
          end;
        end
        else
        begin
          MoveTo(FSizingPos, 0);
          LineTo(FSizingPos, Vert.GridBoundary);
        end;
      finally
        Pen := OldPen;
      end;
    end;
  finally
    OldPen.Free;
  end;
end;

procedure TDzCustomGrid.DrawMove;
var
  OldPen: TPen;
  Pos: Integer;
  R: TRect;
begin
  OldPen := TPen.Create;
  try
    with Canvas do
    begin
      OldPen.Assign(Pen);
      try
        Pen.Style := psDot;
        Pen.Mode := pmXor;
        Pen.Width := 5;
        if FGridState = gsRowMoving then
        begin
          R := CellRect(0, FMovePos);
          if FMovePos > FMoveIndex then
            Pos := R.Bottom
          else
            Pos := R.Top;
          MoveTo(0, Pos);
          LineTo(ClientWidth, Pos);
        end
        else
        begin
          R := CellRect(FMovePos, 0);
          if FMovePos > FMoveIndex then
            if not UseRightToLeftAlignment then
              Pos := R.Right
            else
              Pos := R.Left
          else if not UseRightToLeftAlignment then
            Pos := R.Left
          else
            Pos := R.Right;
          MoveTo(Pos, 0);
          LineTo(Pos, ClientHeight);
        end;
      finally
        Canvas.Pen := OldPen;
      end;
    end;
  finally
    OldPen.Free;
  end;
end;

procedure TDzCustomGrid.FocusCell(ACol, ARow: Longint; MoveAnchor: Boolean);
begin
  MoveCurrent(ACol, ARow, MoveAnchor, True);
  UpdateEdit;
end;

procedure TDzCustomGrid.GridRectToScreenRect(GridRect: TGridRect;
  var ScreenRect: TRect; IncludeLine: Boolean);

  function LinePos(const AxisInfo: TDzGridAxisDrawInfo; Line: Integer): Integer;
  var
    Start, I: Longint;
  begin
    with AxisInfo do
    begin
      Result := 0;
      if Line < FixedCellCount then
        Start := 0
      else
      begin
        if Line >= FirstGridCell then
          Result := FixedBoundary;
        Start := FirstGridCell;
      end;
      for I := Start to Line - 1 do
      begin
        Inc(Result, GetExtent(I) + EffectiveLineWidth);
        if Result > GridExtent then
        begin
          Result := 0;
          Exit;
        end;
      end;
    end;
  end;

  function CalcAxis(const AxisInfo: TDzGridAxisDrawInfo;
    GridRectMin, GridRectMax: Integer;
    var ScreenRectMin, ScreenRectMax: Integer): Boolean;
  begin
    Result := False;
    with AxisInfo do
    begin
      if (GridRectMin >= FixedCellCount) and (GridRectMin < FirstGridCell) then
        if GridRectMax < FirstGridCell then
        begin
          FillChar(ScreenRect, SizeOf(ScreenRect), 0); { erase partial results }
          Exit;
        end
        else
          GridRectMin := FirstGridCell;
      if GridRectMax > LastFullVisibleCell then
      begin
        GridRectMax := LastFullVisibleCell;
        if GridRectMax < GridCellCount - 1 then
          Inc(GridRectMax);
        if LinePos(AxisInfo, GridRectMax) = 0 then
          Dec(GridRectMax);
      end;

      ScreenRectMin := LinePos(AxisInfo, GridRectMin);
      ScreenRectMax := LinePos(AxisInfo, GridRectMax);
      if ScreenRectMax = 0 then
        ScreenRectMax := ScreenRectMin + GetExtent(GridRectMin)
      else
        Inc(ScreenRectMax, GetExtent(GridRectMax));
      if ScreenRectMax > GridExtent then
        ScreenRectMax := GridExtent;
      if IncludeLine then
        Inc(ScreenRectMax, EffectiveLineWidth);
    end;
    Result := True;
  end;

var
  Hold: Integer;
begin
  FillChar(ScreenRect, SizeOf(ScreenRect), 0);
  if (GridRect.Left > GridRect.Right) or (GridRect.Top > GridRect.Bottom) then
    Exit;
  with FCurrentDrawInfo do
  begin
    if GridRect.Left > Horz.LastFullVisibleCell + 1 then
      Exit;
    if GridRect.Top > Vert.LastFullVisibleCell + 1 then
      Exit;

    if CalcAxis(Horz, GridRect.Left, GridRect.Right, ScreenRect.Left,
      ScreenRect.Right) then
    begin
      CalcAxis(Vert, GridRect.Top, GridRect.Bottom, ScreenRect.Top,
        ScreenRect.Bottom);
    end;
  end;
  if UseRightToLeftAlignment and (Canvas.CanvasOrientation = coLeftToRight) then
  begin
    Hold := ScreenRect.Left;
    ScreenRect.Left := ClientWidth - ScreenRect.Right;
    ScreenRect.Right := ClientWidth - Hold;
  end;
end;

procedure TDzCustomGrid.Initialize;
begin
  FTopLeft.X := FixedCols;
  FTopLeft.Y := FixedRows;
  FCurrent := FTopLeft;
  fAnchor := FCurrent;
  if goRowSelect in Options then
    fAnchor.X := ColCount - 1;
end;

procedure TDzCustomGrid.InvalidateCell(ACol, ARow: Longint);
var
  Rect: TGridRect;
begin
  Rect.Top := ARow;
  Rect.Left := ACol;
  Rect.Bottom := ARow;
  Rect.Right := ACol;
  InvalidateRect(Rect);
end;

procedure TDzCustomGrid.InvalidateCol(ACol: Longint);
var
  Rect: TGridRect;
begin
  if not HandleAllocated then
    Exit;
  Rect.Top := 0;
  Rect.Left := ACol;
  Rect.Bottom := VisibleRowCount + 1;
  Rect.Right := ACol;
  InvalidateRect(Rect);
end;

procedure TDzCustomGrid.InvalidateRow(ARow: Longint);
var
  Rect: TGridRect;
begin
  if not HandleAllocated then
    Exit;
  Rect.Top := ARow;
  Rect.Left := 0;
  Rect.Bottom := ARow;
  Rect.Right := VisibleColCount + 1;
  InvalidateRect(Rect);
end;

procedure TDzCustomGrid.InvalidateGrid;
begin
  Invalidate;
end;

procedure TDzCustomGrid.InvalidateRect(ARect: TGridRect);
var
  InvalidRect: TRect;
begin
  if not HandleAllocated then Exit;
  GridRectToScreenRect(ARect, InvalidRect, True);
  Windows.InvalidateRect(Handle, @InvalidRect, False);
end;

procedure TDzCustomGrid.ModifyScrollBar(ScrollBar, ScrollCode, Pos: Integer;
  UseRightToLeft: Boolean);
var
  NewTopLeft: TGridCoord;
  RTLFactor: Integer;

  function Min: Longint;
  begin
    if ScrollBar = SB_HORZ then
      Result := FixedCols
    else
      Result := FixedRows;
  end;

  function Max: Longint;
  begin
    if ScrollBar = SB_HORZ then
      Result := FMaxTopLeft.X
    else
      Result := FMaxTopLeft.Y;
  end;

  function PageUp: Longint;
  var
    mtl: TGridCoord;
  begin
    mtl := CalcMaxTopLeft(FTopLeft, FCurrentDrawInfo);
    if ScrollBar = SB_HORZ then
      Result := FTopLeft.X - mtl.X
    else
      Result := FTopLeft.Y - mtl.Y;
    if Result < 1 then Result := 1;
  end;

  function PageDown: Longint;
  begin
    with FCurrentDrawInfo do
      if ScrollBar = SB_HORZ then
        Result := Horz.LastFullVisibleCell - FTopLeft.X
      else
        Result := Vert.LastFullVisibleCell - FTopLeft.Y;
    if Result < 1 then
      Result := 1;
  end;

  function CalcScrollBar(Value, ARTLFactor: Longint): Longint;
  begin
    Result := Value;
    case ScrollCode of
      SB_LINEUP:
        Dec(Result, ARTLFactor);
      SB_LINEDOWN:
        Inc(Result, ARTLFactor);
      SB_PAGEUP:
        Dec(Result, PageUp * ARTLFactor);
      SB_PAGEDOWN:
        Inc(Result, PageDown * ARTLFactor);
      SB_THUMBPOSITION, SB_THUMBTRACK:
        if ((ScrollCode = SB_THUMBPOSITION) or
          (goThumbTracking in Options)) then
        begin
          if (not UseRightToLeftAlignment) or (ARTLFactor = 1) then
            Result := Min + Pos
          else
            Result := Max - Pos;
        end;
      SB_BOTTOM:
        Result := Max;
      SB_TOP:
        Result := Min;
    end;
  end;

  procedure ModifyPixelScrollBar(Code, Pos: Cardinal);
  var
    NewOffset: Integer;
    OldOffset: Integer;
    R: TGridRect;
    GridSpace, ColWidth: Integer;
  begin
    NewOffset := FColOffset;
    ColWidth := ColWidths[FCurrentDrawInfo.Horz.FirstGridCell];
    GridSpace := ClientWidth - FCurrentDrawInfo.Horz.FixedBoundary;
    case Code of
      SB_LINEUP: Dec(NewOffset, Canvas.TextWidth('0') * RTLFactor);
      SB_LINEDOWN: Inc(NewOffset, Canvas.TextWidth('0') * RTLFactor);
      SB_PAGEUP: Dec(NewOffset, GridSpace * RTLFactor);
      SB_PAGEDOWN: Inc(NewOffset, GridSpace * RTLFactor);
      SB_THUMBPOSITION,
        SB_THUMBTRACK:
        if (goThumbTracking in Options) or (Code = SB_THUMBPOSITION) then
        begin
          if not UseRightToLeftAlignment then
            NewOffset := Pos
          else
            NewOffset := Max - Integer(Pos);
        end;
      SB_BOTTOM: NewOffset := 0;
      SB_TOP: NewOffset := ColWidth - GridSpace;
    end;
    if NewOffset < 0 then
      NewOffset := 0
    else if NewOffset >= ColWidth - GridSpace then
      NewOffset := ColWidth - GridSpace;
    if NewOffset <> FColOffset then
    begin
      OldOffset := FColOffset;
      FColOffset := NewOffset;
      ScrollData(OldOffset - NewOffset, 0);
      FillChar(R, SizeOf(R), 0);
      R.Bottom := FixedRows;
      InvalidateRect(R);
      Update;
      UpdateScrollPos;
    end;
  end;

var
  Temp: Longint;
begin
  if (not UseRightToLeftAlignment) or (not UseRightToLeft) then RTLFactor := 1
  else RTLFactor := -1;
  if Visible and CanFocus and TabStop and
    not (csDesigning in ComponentState) then SetFocus;
  if (ScrollBar = SB_HORZ) and (ColCount = 1) then
  begin
    ModifyPixelScrollBar(ScrollCode, Pos);
    Exit;
  end;
  NewTopLeft := FTopLeft;
  if (ScrollBar = SB_HORZ) then
  begin
    repeat
      Temp := NewTopLeft.X;
      NewTopLeft.X := CalcScrollBar(NewTopLeft.X, RTLFactor);
    until (NewTopLeft.X <= FixedCols) or (NewTopLeft.X >= FMaxTopLeft.X)
      or (ColWidths[NewTopLeft.X] > 0) or (Temp = NewTopLeft.X);
  end
  else begin
    repeat
      Temp := NewTopLeft.Y;
      NewTopLeft.Y := CalcScrollBar(NewTopLeft.Y, 1);
    until (NewTopLeft.Y <= FixedRows) or (NewTopLeft.Y >= FMaxTopLeft.Y)
      or (RowHeights[NewTopLeft.Y] > 0) or (Temp = NewTopLeft.Y);
  end;
  NewTopLeft.X := Math.Max(FixedCols, Math.Min(FMaxTopLeft.X, NewTopLeft.X));
  NewTopLeft.Y := Math.Max(FixedRows, Math.Min(FMaxTopLeft.Y, NewTopLeft.Y));
  if (NewTopLeft.X <> FTopLeft.X) or (NewTopLeft.Y <> FTopLeft.Y) then
    MoveTopLeft(NewTopLeft.X, NewTopLeft.Y);
end;

procedure TDzCustomGrid.MoveAdjust(var CellPos: Longint; FromIndex, ToIndex:
  Longint);
var
  Min, Max: Longint;
begin
  if CellPos = FromIndex then
    CellPos := ToIndex
  else
  begin
    Min := FromIndex;
    Max := ToIndex;
    if FromIndex > ToIndex then
    begin
      Min := ToIndex;
      Max := FromIndex;
    end;
    if (CellPos >= Min) and (CellPos <= Max) then
      if FromIndex > ToIndex then
        Inc(CellPos)
      else
        Dec(CellPos);
  end;
end;

procedure TDzCustomGrid.MoveAnchor(const NewAnchor: TGridCoord);
var
  OldSel: TGridRect;
begin
  if [goRangeSelect, goEditing] * Options = [goRangeSelect] then
  begin
    OldSel := Selection;
    fAnchor := NewAnchor;
    if (goRowSelect in Options) then
    begin
      fAnchor.X := ColCount - 1;
    end;
    ClampInView(NewAnchor);
    SelectionMoved(OldSel);
  end
  else
    MoveCurrent(NewAnchor.X, NewAnchor.Y, True, True);
end;

procedure TDzCustomGrid.MoveCurrent(ACol, ARow: Longint; MoveAnchor,
  Show: Boolean);
var
  OldSel: TGridRect;
  OldCurrent: TGridCoord;
begin
  if (ACol < 0) or (ARow < 0) or (ACol >= ColCount) or
    (ARow >= RowCount) then InvalidOp(SIndexOutOfRange);
  if SelectCell(ACol, ARow) then
  begin
    OldSel := Selection;
    OldCurrent := FCurrent;
    FCurrent.X := ACol;
    FCurrent.Y := ARow;
    if not (goAlwaysShowEditor in Options) then HideEditor;
    if MoveAnchor or not (goRangeSelect in Options) then
    begin
      fAnchor := FCurrent;
      if goRowSelect in Options then fAnchor.X := ColCount - 1;
    end;
    if Show then ClampInView(FCurrent);
    SelectionMoved(OldSel);
    with OldCurrent do
      InvalidateCell(X, Y);
    with FCurrent do
      InvalidateCell(ACol, ARow);
  end;
end;

procedure TDzCustomGrid.MoveTopLeft(ALeft, ATop: Longint);
var
  OldTopLeft: TGridCoord;
begin
  if (ALeft = FTopLeft.X) and (ATop = FTopLeft.Y) then Exit;
  Update;
  OldTopLeft := FTopLeft;
  FTopLeft.X := ALeft;
  FTopLeft.Y := ATop;
  TopLeftMoved(OldTopLeft);
end;

procedure TDzCustomGrid.ResizeCol(Index: Longint; OldSize, NewSize: Integer);
begin
  UpdateMaxTopLeft;
  InvalidateGrid;
end;

procedure TDzCustomGrid.ResizeRow(Index: Longint; OldSize, NewSize: Integer);
begin
  UpdateMaxTopLeft;
  InvalidateGrid;
end;

procedure TDzCustomGrid.SelectionChanged;
begin

end;

procedure TDzCustomGrid.SelectionMoved(const OldSel: TGridRect);
var
  OldRect, NewRect: TRect;
  AXorRects: TXorRects;
  I: Integer;
begin
  if not HandleAllocated then Exit;
  GridRectToScreenRect(OldSel, OldRect, True);
  GridRectToScreenRect(Selection, NewRect, True);
  XorRects(OldRect, NewRect, AXorRects);
  for I := Low(AXorRects) to High(AXorRects) do
    Windows.InvalidateRect(Handle, @AXorRects[I], False);
end;

procedure TDzCustomGrid.ScrollDataInfo(DX, DY: Integer;
  var DrawInfo: TDzGridDrawInfo);
var
  ScrollArea: TRect;
  ScrollFlags: Integer;
begin
  with DrawInfo do
  begin
    ScrollFlags := SW_INVALIDATE;
    if not DefaultDrawing then
      ScrollFlags := ScrollFlags or SW_ERASE;
    { Scroll the area }
    if DY = 0 then
    begin
      { Scroll both the column titles and data area at the same time }
      if not UseRightToLeftAlignment then
        ScrollArea := Rect(Horz.FixedBoundary, 0, Horz.GridExtent,
          Vert.GridExtent)
      else
      begin
        ScrollArea := Rect(ClientWidth - Horz.GridExtent, 0, ClientWidth -
          Horz.FixedBoundary, Vert.GridExtent);
        DX := -DX;
      end;
      ScrollWindowEx(Handle, DX, 0, @ScrollArea, @ScrollArea, 0, nil,
        ScrollFlags);
    end
    else if DX = 0 then
    begin
      { Scroll both the row titles and data area at the same time }
      ScrollArea := Rect(0, Vert.FixedBoundary, Horz.GridExtent,
        Vert.GridExtent);
      ScrollWindowEx(Handle, 0, DY, @ScrollArea, @ScrollArea, 0, nil,
        ScrollFlags);
    end
    else
    begin
      { Scroll titles and data area separately }
      { Column titles }
      ScrollArea := Rect(Horz.FixedBoundary, 0, Horz.GridExtent,
        Vert.FixedBoundary);
      ScrollWindowEx(Handle, DX, 0, @ScrollArea, @ScrollArea, 0, nil,
        ScrollFlags);
      { Row titles }
      ScrollArea := Rect(0, Vert.FixedBoundary, Horz.FixedBoundary,
        Vert.GridExtent);
      ScrollWindowEx(Handle, 0, DY, @ScrollArea, @ScrollArea, 0, nil,
        ScrollFlags);
      { Data area }
      ScrollArea := Rect(Horz.FixedBoundary, Vert.FixedBoundary,
        Horz.GridExtent,
        Vert.GridExtent);
      ScrollWindowEx(Handle, DX, DY, @ScrollArea, @ScrollArea, 0, nil,
        ScrollFlags);
    end;
  end;
  if goRowSelect in Options then
    InvalidateRect(Selection);
end;

function TDzCustomGrid.ScrollBarVisible(Code: Word): Boolean;
var
  Min, Max: Integer;
begin
  Result := False;
  if (ScrollBars = ssBoth) or
    ((Code = SB_HORZ) and (ScrollBars = ssHorizontal)) or
    ((Code = SB_VERT) and (ScrollBars = ssVertical)) then
  begin
    GetScrollRange(Handle, Code, Min, Max);
    Result := Min <> Max;
  end;
end;

procedure TDzCustomGrid.ScrollData(DX, DY: Integer);
begin
  ScrollDataInfo(DX, DY, FCurrentDrawInfo);
end;

procedure TDzCustomGrid.TopLeftMoved(const OldTopLeft: TGridCoord);

  function CalcScroll(const AxisInfo: TDzGridAxisDrawInfo;
    OldPos, CurrentPos: Integer; var Amount: Longint): Boolean;
  var
    Start, Stop: Longint;
    I: Longint;
  begin
    Result := False;
    with AxisInfo do
    begin
      if OldPos < CurrentPos then
      begin
        Start := OldPos;
        Stop := CurrentPos;
      end
      else begin
        Start := CurrentPos;
        Stop := OldPos;
      end;
      Amount := 0;
      for I := Start to Stop - 1 do
      begin
        Inc(Amount, GetExtent(I) + EffectiveLineWidth);
        if Amount > (GridBoundary - FixedBoundary) then
        begin
          { Scroll amount too big, redraw the whole thing }
          InvalidateGrid;
          Exit;
        end;
      end;
      if OldPos < CurrentPos then Amount := -Amount;
    end;
    Result := True;
  end;

var
  Delta: TGridCoord;
begin
  UpdateDrawInfo;
  UpdateScrollPos;
  with FCurrentDrawInfo do
    if CalcScroll(Horz, OldTopLeft.X, FTopLeft.X, Delta.X) and
      CalcScroll(Vert, OldTopLeft.Y, FTopLeft.Y, Delta.Y) then
      ScrollDataInfo(Delta.X, Delta.Y, FCurrentDrawInfo);
  TopLeftChanged;
end;

procedure TDzCustomGrid.UpdateScrollPos;
var
  GridSpace, ColWidth: Integer;

  procedure SetScroll(Code: Word; Value: Integer);
  begin
    if UseRightToLeftAlignment and (Code = SB_HORZ) then
    begin
      if (ColCount <> 1) then
        Value := MaxShortInt - Value
      else
        Value := (ColWidth - GridSpace) - Value;
    end;
    if GetScrollPos(Handle, Code) <> Value then
      SetScrollPos(Handle, Code, Value, True);
  end;

begin
  if (not HandleAllocated) or (ScrollBars = ssNone) then Exit;
  if ScrollBars in [ssHorizontal, ssBoth] then
    with FCurrentDrawInfo do
    begin
      if (ColCount = 1) then
      begin
        ColWidth := ColWidths[Horz.FirstGridCell];
        GridSpace := ClientWidth - Horz.FixedBoundary;
        if (FColOffset > 0) and (GridSpace > (ColWidth - FColOffset)) then
          ModifyScrollbar(SB_HORZ, SB_THUMBPOSITION, ColWidth - GridSpace, True)
        else SetScroll(SB_HORZ, FColOffset);
      end
      else SetScroll(SB_HORZ, FTopLeft.X - FixedCols);
    end;
  if ScrollBars in [ssVertical, ssBoth] then
    SetScroll(SB_VERT, FTopLeft.Y - FixedRows);
end;

function TDzCustomGrid.CreateEditor: TDzInplaceEdit;
begin
  Result := TDzInplaceEdit.Create(Self);
end;

procedure TDzCustomGrid.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do
  begin
    Style := Style or WS_TABSTOP;
    if FScrollBars in [ssVertical, ssBoth] then
      Style := Style or WS_VSCROLL;
    if FScrollBars in [ssHorizontal, ssBoth] then
      Style := Style or WS_HSCROLL;
    WindowClass.style := CS_DBLCLKS;
    if FBorderStyle = bsSingle then
      if NewStyleControls and Ctl3D then
      begin
        Style := Style and not WS_BORDER;
        ExStyle := ExStyle or WS_EX_CLIENTEDGE;
      end
      else
        Style := Style or WS_BORDER;
  end;
end;

procedure TDzCustomGrid.KeyDown(var Key: Word; Shift: TShiftState);
var
  NewTopLeft, NewCurrent: TGridCoord;
  PageWidth, PageHeight: Integer;

  procedure CalcPageExtents;
  begin
    PageWidth := FCurrentDrawInfo.Horz.LastFullVisibleCell - LeftCol;
    if PageWidth < 1 then PageWidth := 1;
    PageHeight := FCurrentDrawInfo.Vert.LastFullVisibleCell - TopRow;
    if PageHeight < 1 then PageHeight := 1;
  end;

  procedure Restrict(var Coord: TGridCoord; MinX, MinY, MaxX, MaxY: Longint);
  begin
    with Coord do
    begin
      if X > MaxX then
        X := MaxX
      else if X < MinX then
        X := MinX;
      if Y > MaxY then
        Y := MaxY
      else if Y < MinY then
        Y := MinY;
    end;
  end;

begin
  inherited KeyDown(Key, Shift);
  if not CanGridAcceptKey(Key, Shift) then
  begin
    Key := 0;
    Exit;
  end;
  NewTopLeft := FTopLeft;
  NewCurrent := FCurrent;
  CalcPageExtents;
  case Key of
    VK_UP:
      begin
        if ssShift in Shift then DoSelect(fAnchor.Y - 1, Shift)
        else DoSelect(FCurrent.Y - 1, Shift);
        FGridState := gsNormal;
        Exit;
        Dec(NewTopLeft.Y);
      end;
    VK_DOWN:
      begin
        if ssShift in Shift then DoSelect(fAnchor.Y + 1, Shift)
        else DoSelect(FCurrent.Y + 1, Shift);
        FGridState := gsNormal;
        Exit;
        Inc(NewTopLeft.Y);
      end;
    VK_PRIOR:
      begin
        Dec(NewTopLeft.Y, PageHeight);
      end;
    VK_NEXT:
      begin
        Inc(NewTopLeft.Y, PageHeight);
      end;
    VK_HOME:
      begin
        NewTopLeft.Y := FixedRows;
      end;
    VK_END:
      begin
        NewTopLeft.Y := RowCount - 1;
      end;
  end;
  Restrict(NewTopLeft, FixedCols, FixedRows, FMaxTopLeft.X, FMaxTopLeft.Y);
  if (NewTopLeft.X <> LeftCol) or (NewTopLeft.Y <> TopRow) then
    MoveTopLeft(NewTopLeft.X, NewTopLeft.Y);
  Restrict(NewCurrent, FixedCols, FixedRows, ColCount - 1, RowCount - 1);
  if (NewCurrent.X <> Col) or (NewCurrent.Y <> Row) then
    FocusCell(NewCurrent.X, NewCurrent.Y, not (ssShift in Shift));
end;

procedure TDzCustomGrid.KeyPress(var Key: Char);
begin
  inherited KeyPress(Key);
  if not (goAlwaysShowEditor in Options) and (Key = #13) then
  begin
    if FEditorMode then
      HideEditor
    else
      ShowEditor;
    Key := #0;
  end;
end;

procedure TDzCustomGrid.MouseDown(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  CellHit: TGridCoord;
  MoveDrawn: Boolean;
begin
  MoveDrawn := False;
  HideEdit;
  if not (csDesigning in ComponentState) and
    (CanFocus or (GetParentForm(Self) = nil)) then
  begin
    SetFocus;
    if not IsActiveControl then
    begin
      MouseCapture := False;
      Exit;
    end;
  end;
  if Button = mbLeft then
  begin
    CellHit := MouseCoord(X, Y);
    FMouseDownCoord := CellHit;
    { Check grid sizing }
    CalcSizingState(X, Y, FGridState, FSizingIndex,
      FSizingPos, FSizingOfs, FCurrentDrawInfo);
    if FGridState <> gsNormal then
    begin
      if (FGridState = gsColSizing) and UseRightToLeftAlignment then
        FSizingPos := ClientWidth - FSizingPos;
      DrawSizingLine(FCurrentDrawInfo);
      Exit;
    end;

    if ssDouble in Shift then DblClickCell(CellHit.X, CellHit.Y)
    else begin
      if (CellHit.X >= FixedCols) and (CellHit.Y >= FixedRows) then
      begin
        if (goEditing in Options) then
        begin
          if (CellHit.X = FCurrent.X) and (CellHit.Y = FCurrent.Y) then
            ShowEditor
          else begin
            MoveCurrent(CellHit.X, CellHit.Y, True, True);
            UpdateEdit;
          end;
        end
        else if CanDoSelect(CellHit.X, CellHit.Y) then
        begin
          DoSelect(CellHit.Y, Shift);
          SetTimer(Handle, 1, 60, nil);
        end;
      end
      else if (goRowMoving in Options) and (CellHit.X >= 0) and
        (CellHit.X < FixedCols) and (CellHit.Y >= FixedRows) then
      begin
        FMoveIndex := CellHit.Y;
        FMovePos := FMoveIndex;
        if BeginRowDrag(FMoveIndex, FMovePos, Point(X, Y)) then
        begin
          FGridState := gsRowMoving;
          Update;
          DrawMove;
          MoveDrawn := True;
          SetTimer(Handle, 1, 60, nil);
        end;
      end
      else if (goColMoving in Options) and (CellHit.Y >= 0) and
        (CellHit.Y < FixedRows) and (CellHit.X >= FixedCols) then
      begin
        FMoveIndex := CellHit.X;
        FMovePos := FMoveIndex;
        if BeginColumnDrag(FMoveIndex, FMovePos, Point(X, Y)) then
        begin
          FGridState := gsColMoving;
          Update;
          DrawMove;
          MoveDrawn := True;
          SetTimer(Handle, 1, 60, nil);
        end;
      end;
    end;
  end
  else begin
    //right mouse button 
  end;
  try
    inherited MouseDown(Button, Shift, X, Y);
  except
    if MoveDrawn then DrawMove;
  end;
end;

procedure TDzCustomGrid.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  CellHit: TGridCoord;
  min, max: Integer;
begin
  with FCurrentDrawInfo do
  begin
    case FGridState of
      gsSelecting, gsColMoving, gsRowMoving:
        begin
          CellHit := MouseCoord(X, Y);
          if (CellHit.X >= FixedCols) and (CellHit.Y >= FixedRows) and
            (CellHit.X <= Horz.LastFullVisibleCell + 1) and
            (CellHit.Y <= Vert.LastFullVisibleCell + 1) then
            case FGridState of
              gsSelecting:
                if ((CellHit.X <> fAnchor.X) or (CellHit.Y <> fAnchor.Y)) then
                begin
                  if ((goRowSelect in Options) and (CellHit.Y <> fAnchor.Y)) then
                  begin
                    if (fAnchor.Y > FCurrent.Y) then
                    begin
                      min := FCurrent.Y;
                      max := fAnchor.Y;
                    end
                    else begin
                      min := fAnchor.Y;
                      max := FCurrent.Y;
                    end;
                    if (ssCtrl in Shift) then
                      FSelRows.RemoveRange(min, max)
                    else
                      FSelRows.Clear;
                    if (CellHit.Y < FCurrent.Y) then
                      FSelRows.AddRange(CellHit.Y, FCurrent.Y)
                    else
                      FSelRows.AddRange(FCurrent.Y, CellHit.Y);
                    InvalidateGrid;
                  end;
                  MoveAnchor(CellHit);
                end;
              gsColMoving:
                begin
                  MoveAndScroll(X, CellHit.X, FCurrentDrawInfo, Horz,
                    SB_HORZ, Point(X, Y));
                end;
              gsRowMoving:
                begin
                  MoveAndScroll(Y, CellHit.Y, FCurrentDrawInfo, Vert,
                    SB_VERT, Point(X, Y));
                end;
            end;
        end;
      gsRowSizing, gsColSizing:
        begin
          DrawSizingLine(FCurrentDrawInfo); { XOR it out }
          if FGridState = gsRowSizing then
            FSizingPos := Y + FSizingOfs
          else
            FSizingPos := X + FSizingOfs;
          DrawSizingLine(FCurrentDrawInfo); { XOR it back in }
        end;
    end;
  end;
  inherited MouseMove(Shift, X, Y);
end;

procedure TDzCustomGrid.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
var
  NewSize: Integer;
  CellHit: TGridCoord;

  function ResizeLine(const AxisInfo: TDzGridAxisDrawInfo): Integer;
  var
    I: Integer;
  begin
    with AxisInfo do
    begin
      Result := FixedBoundary;
      for I := FirstGridCell to FSizingIndex - 1 do
        Inc(Result, GetExtent(I) + EffectiveLineWidth);
      Result := FSizingPos - Result;
    end;
  end;

  procedure TestCellClick;
  begin
    CellHit := MouseCoord(X, Y);
    if (FMouseDownCoord.X = CellHit.X) and
      (FMouseDownCoord.Y = CellHit.Y) and
      (CellHit.X >= 0) and (CellHit.Y >= 0) then
    begin
      ClickCell(CellHit.X, CellHit.Y);
    end;
  end;
begin
  try
    if Button = mbLeft then
    case FGridState of
      gsSelecting:
        begin
          MouseMove(Shift, X, Y);
          KillTimer(Handle, 1);
          UpdateEdit;
          TestCellClick;
        end;
      gsRowSizing, gsColSizing:
        begin
          UpdateDrawInfo;
          DrawSizingLine(FCurrentDrawInfo);
          if (FGridState = gsColSizing) and UseRightToLeftAlignment then
            FSizingPos := ClientWidth - FSizingPos;
          if FGridState = gsColSizing then
          begin
            NewSize := ResizeLine(FCurrentDrawInfo.Horz);
            if NewSize > 1 then
            begin
              ColWidths[FSizingIndex] := NewSize;
              UpdateDesigner;
            end;
          end
          else begin
            NewSize := ResizeLine(FCurrentDrawInfo.Vert);
            if NewSize > 1 then
            begin
              RowHeights[FSizingIndex] := NewSize;
              UpdateDesigner;
            end;
          end;
        end;
      gsColMoving:
        begin
          DrawMove;
          KillTimer(Handle, 1);
          if EndColumnDrag(FMoveIndex, FMovePos, Point(X, Y))
            and (FMoveIndex <> FMovePos) then
          begin
            MoveColumn(FMoveIndex, FMovePos);
            UpdateDesigner;
          end
          else begin
            TestCellClick;
          end;
          UpdateEdit;
        end;
      gsRowMoving:
        begin
          DrawMove;
          KillTimer(Handle, 1);
          if EndRowDrag(FMoveIndex, FMovePos, Point(X, Y))
            and (FMoveIndex <> FMovePos) then
          begin
            MoveRow(FMoveIndex, FMovePos);
            UpdateDesigner;
          end
          else begin
            TestCellClick;
          end;
          UpdateEdit;
        end;
      else begin
        TestCellClick;
        UpdateEdit;
      end;
    end;
    inherited MouseUp(Button, Shift, X, Y);
  finally
    FGridState := gsNormal;
  end;
end;

procedure TDzCustomGrid.MoveAndScroll(Mouse, CellHit: Integer;
  var DrawInfo: TDzGridDrawInfo; var Axis: TDzGridAxisDrawInfo;
  ScrollBar: Integer; const MousePt: TPoint);
begin
  if UseRightToLeftAlignment and (ScrollBar = SB_HORZ) then
    Mouse := ClientWidth - Mouse;
  if (CellHit <> FMovePos) and
    not ((FMovePos = Axis.FixedCellCount) and (Mouse < Axis.FixedBoundary)) and
    not ((FMovePos = Axis.GridCellCount - 1) and (Mouse > Axis.GridBoundary))
    then
  begin
    DrawMove; // hide the drag line
    if (Mouse < Axis.FixedBoundary) then
    begin
      if (FMovePos > Axis.FixedCellCount) then
      begin
        ModifyScrollbar(ScrollBar, SB_LINEUP, 0, False);
        Update;
        CalcDrawInfo(DrawInfo); // this changes contents of Axis var
      end;
      CellHit := Axis.FirstGridCell;
    end
    else if (Mouse >= Axis.FullVisBoundary) then
    begin
      if (FMovePos = Axis.LastFullVisibleCell) and
        (FMovePos < Axis.GridCellCount - 1) then
      begin
        ModifyScrollBar(Scrollbar, SB_LINEDOWN, 0, False);
        Update;
        CalcDrawInfo(DrawInfo); // this changes contents of Axis var
      end;
      CellHit := Axis.LastFullVisibleCell;
    end
    else if CellHit < 0 then
      CellHit := FMovePos;
    if ((FGridState = gsColMoving) and CheckColumnDrag(FMoveIndex, CellHit,
      MousePt))
      or ((FGridState = gsRowMoving) and CheckRowDrag(FMoveIndex, CellHit,
      MousePt)) then
      FMovePos := CellHit;
    DrawMove;
  end;
end;

function TDzCustomGrid.GetColWidths(Index: Longint): Integer;
begin
  if (FColWidths = nil) or (Index >= ColCount) then
    Result := DefaultColWidth
  else
    Result := PIntArray(FColWidths)^[Index + 1];
end;

function TDzCustomGrid.GetRowHeights(Index: Longint): Integer;
begin
  if (FRowHeights = nil) or (Index >= RowCount) then
    Result := DefaultRowHeight
  else
    Result := PIntArray(FRowHeights)^[Index + 1];
end;

function TDzCustomGrid.GetGridWidth: Integer;
begin
  Result := FCurrentDrawInfo.Horz.GridBoundary;
end;

function TDzCustomGrid.GetGridHeight: Integer;
begin
  Result := FCurrentDrawInfo.Vert.GridBoundary;
end;

function TDzCustomGrid.GetSelection: TGridRect;
begin
  Result := GridRect(FCurrent, fAnchor);
end;

function TDzCustomGrid.GetSelRow(index: Integer): Integer;
begin
  Result := FSelRows.Items[index];
end;

function TDzCustomGrid.GetSelRowCount: Integer;
begin
  Result := FSelRows.Count;
end;

function TDzCustomGrid.GetTabStops(Index: Longint): Boolean;
begin
  if FTabStops = nil then
    Result := True
  else
    Result := Boolean(PIntArray(FTabStops)^[Index + 1]);
end;

function TDzCustomGrid.GetVisibleColCount: Integer;
begin
  Result := FCurrentDrawInfo.Horz.LastFullVisibleCell - LeftCol + 1;
end;

function TDzCustomGrid.GetVisibleRowCount: Integer;
begin
  Result := FCurrentDrawInfo.Vert.LastFullVisibleCell - TopRow + 1;
end;

procedure TDzCustomGrid.SetBorderStyle(Value: TBorderStyle);
begin
  if FBorderStyle <> Value then
  begin
    FBorderStyle := Value;
    RecreateWnd;
  end;
end;

procedure TDzCustomGrid.SetCol(Value: Longint);
begin
  if Col <> Value then
    FocusCell(Value, Row, True);
end;

procedure TDzCustomGrid.SetColCount(Value: Longint);
begin
  if (FColCount <> Value) then
  begin
    if Value < 1 then Value := 1;
    if Value <= FixedCols then
      FixedCols := Value - 1;
    ChangeSize(Value, RowCount);
    if goRowSelect in Options then
    begin
      fAnchor.X := ColCount - 1;
      Invalidate;
    end;
  end;
end;

procedure TDzCustomGrid.SetColWidths(Index: Longint; Value: Integer);
begin
  if FColWidths = nil then
    UpdateExtents(FColWidths, ColCount, DefaultColWidth);
  if Index >= ColCount then
    InvalidOp(SIndexOutOfRange);
  if Value <> PIntArray(FColWidths)^[Index + 1] then
  begin
    ResizeCol(Index, PIntArray(FColWidths)^[Index + 1], Value);
    PIntArray(FColWidths)^[Index + 1] := Value;
    ColWidthsChanged(Index);
  end;
end;

procedure TDzCustomGrid.SetDefaultColWidth(Value: Integer);
begin
  if FColWidths <> nil then
    UpdateExtents(FColWidths, 0, 0);
  FDefaultColWidth := Value;
  ColWidthsChanged(-1);
  InvalidateGrid;
end;

procedure TDzCustomGrid.SetDefaultRowHeight(Value: Integer);
begin
  if FRowHeights <> nil then
    UpdateExtents(FRowHeights, 0, 0);
  FDefaultRowHeight := Value;
  RowHeightsChanged;
  InvalidateGrid;
end;

procedure TDzCustomGrid.SetFixedColor(Value: TColor);
begin
  if FFixedColor <> Value then
  begin
    FFixedColor := Value;
    InvalidateGrid;
  end;
end;

procedure TDzCustomGrid.SetFixedCols(Value: Integer);
begin
  if (FFixedCols <> Value) then
  begin
    if (Value < 0) then InvalidOp(SIndexOutOfRange);
    if (Value >= ColCount) then InvalidOp(SFixedColTooBig);
    FFixedCols := Value;
    Initialize;
    InvalidateGrid;
  end;
end;

procedure TDzCustomGrid.SetFixedRows(Value: Integer);
begin
  if FFixedRows <> Value then
  begin
    if (Value < 0) then InvalidOp(SIndexOutOfRange);
    if (Value > RowCount) then InvalidOp(SFixedRowTooBig);
    FFixedRows := Value;
    Initialize;
    InvalidateGrid;
  end;
end;

procedure TDzCustomGrid.SetEditorMode(Value: Boolean);
begin
  if not Value then HideEditor
  else begin
    ShowEditor;
    if (FInplaceEdit <> nil) then FInplaceEdit.Deselect;
  end;
end;

procedure TDzCustomGrid.SetGridScale(ccnt, rcnt: Integer);
var
  changed: Boolean;
begin
  changed := False;
  if (FRowCount <> rcnt) then
  begin
    if rcnt < 1 then rcnt := 1;
    if rcnt <= FixedRows then FixedRows := rcnt - 1;
    changed := True;
  end;
  if (FColCount <> ccnt) then
  begin
    if ccnt < 1 then ccnt := 1;
    if ccnt <= FixedCols then FixedCols := ccnt - 1;
    changed := True;
  end;
  if (changed) then ChangeSize(ccnt, rcnt);
  {if goRowSelect in Options then
  begin
    fAnchor.X := ColCount - 1;
    Invalidate;
  end;}
end;

procedure TDzCustomGrid.SetHighLightColor(const Value: TColor);
begin
  FHighLightColor := Value;
end;

procedure TDzCustomGrid.SetHightLightTextColor(const Value: TColor);
begin
  FHighLightTextColor := Value;
end;

procedure TDzCustomGrid.SetHorzLineWidth(const Value: Integer);
begin
  if FHorzLineWidth <> Value then
  begin
    FHorzLineWidth := Value;
    InvalidateGrid;
  end;
end;

procedure TDzCustomGrid.SetLeftCol(Value: Longint);
begin
  if FTopLeft.X <> Value then
    MoveTopLeft(Value, TopRow);
end;

procedure TDzCustomGrid.SetOptions(Value: TGridOptions);
begin
  if FOptions <> Value then
  begin
    if goRowSelect in Value then
      Exclude(Value, goAlwaysShowEditor);
    FOptions := Value;
    if not FEditorMode then
      if goAlwaysShowEditor in Value then
        ShowEditor
      else
        HideEditor;
    if goRowSelect in Value then
      MoveCurrent(Col, Row, True, False);
    InvalidateGrid;
  end;
end;

procedure TDzCustomGrid.SetRow(Value: Longint);
begin
  if Row <> Value then
    FocusCell(Col, Value, True);
end;

procedure TDzCustomGrid.SetRowCount(Value: Longint);
begin
  if FRowCount <> Value then
  begin
    if Value < 1 then Value := 1;
    if Value < FixedRows then FixedRows := Value;
    if (Value < RowCount) then
      FSelRows.RemoveRange(Value - 1, RowCount);
    ChangeSize(ColCount, Value);
  end;
end;

procedure TDzCustomGrid.SetRowHeights(Index: Longint; Value: Integer);
begin
  if FRowHeights = nil then
    UpdateExtents(FRowHeights, RowCount, DefaultRowHeight);
  if Index >= RowCount then
    InvalidOp(SIndexOutOfRange);
  if Value <> PIntArray(FRowHeights)^[Index + 1] then
  begin
    ResizeRow(Index, PIntArray(FRowHeights)^[Index + 1], Value);
    PIntArray(FRowHeights)^[Index + 1] := Value;
    RowHeightsChanged;
  end;
end;

procedure TDzCustomGrid.SetScrollBars(Value: TScrollStyle);
begin
  if FScrollBars <> Value then
  begin
    FScrollBars := Value;
    RecreateWnd;
  end;
end;

procedure TDzCustomGrid.SetSelection(Value: TGridRect);
var
  OldSel: TGridRect;
begin
  OldSel := Selection;
  fAnchor := Value.TopLeft;
  FCurrent := Value.BottomRight;
  SelectionMoved(OldSel);
end;

procedure TDzCustomGrid.SetTabStops(Index: Longint; Value: Boolean);
begin
  if FTabStops = nil then
    UpdateExtents(FTabStops, ColCount, Integer(True));
  if Index >= ColCount then
    InvalidOp(SIndexOutOfRange);
  PIntArray(FTabStops)^[Index + 1] := Integer(Value);
end;

procedure TDzCustomGrid.SetTopRow(Value: Longint);
begin
  if FTopLeft.Y <> Value then
    MoveTopLeft(LeftCol, Value);
end;

procedure TDzCustomGrid.SetVertLineWidth(const Value: Integer);
begin
  if FVertLineWidth <> Value then
  begin
    FVertLineWidth := Value;
    InvalidateGrid;
  end;
end;

procedure TDzCustomGrid.HideEdit;
begin
  if FInplaceEdit <> nil then
  try
    UpdateText;
  finally
    FInplaceCol := -1;
    FInplaceRow := -1;
    FInplaceEdit.Hide;
  end;
end;

procedure TDzCustomGrid.UpdateEdit;

  procedure UpdateEditor;
  begin
    FInplaceCol := Col;
    FInplaceRow := Row;
    FInplaceEdit.UpdateContents;
    if FInplaceEdit.MaxLength = -1 then
      FReadOnly := False
    else
      FReadOnly := True;
    FInplaceEdit.SelectAll;
  end;

begin
  if CanEditShow then
  begin
    if FInplaceEdit = nil then
    begin
      FInplaceEdit := CreateEditor;
      FInplaceEdit.SetGrid(Self);
      FInplaceEdit.Parent := Self;
      UpdateEditor;
    end
    else begin
      if (Col <> FInplaceCol) or (Row <> FInplaceRow) then
      begin
        HideEdit;
        UpdateEditor;
      end;
    end;
    if CanEditShow then
      FInplaceEdit.Move(CellRect(Col, Row));
  end;
end;

procedure TDzCustomGrid.UpdateMaxTopLeft;
var
  MaxTopLeft, OldTopLeft: TGridCoord;
  hscroll, vscroll: Boolean;

  procedure SetAxisRange(var Max, Old, Current: Longint; Code: Word;
    Fixeds: Integer; redraw: Boolean);
  begin
    if Fixeds < Max then
      SetScrollRange(Handle, Code, 0, Max - Fixeds, redraw)
    else
      SetScrollRange(Handle, Code, 0, 0, redraw);
    if Old > Max then Current := Max;
  end;

  procedure CalcHorzSizeInfo(extent: Integer);
  begin
    CalcFixedAxis(FCurrentDrawInfo.Horz, [goFixedVertLine, goVertLine],
      FixedCols, LeftCol, ColCount, VertLineWidth, GetColWidths);
    CalcAxis(FCurrentDrawInfo.Horz, extent);
    FMaxTopLeft.X := CalcMaxCell(FCurrentDrawInfo.Horz, ColCount - 1);
  end;

  procedure CalcVertSizeInfo(extent: Integer);
  begin
    CalcFixedAxis(FCurrentDrawInfo.Vert, [goFixedHorzLine, goHorzLine],
      FixedRows, TopRow, RowCount, HorzLineWidth, GetRowHeights);
    CalcAxis(FCurrentDrawInfo.Vert, ClientHeight);
    FMaxTopLeft.Y := CalcMaxCell(FCurrentDrawInfo.Vert, RowCount - 1);
  end;

  procedure SetHorzRange(redraw: Boolean);
  var
    Range: Integer;
  begin
    if (ColCount = 1) then
    begin
      Range := ColWidths[0] - ClientWidth;
      if (Range < 0) then Range := 0;
      hscroll := Range > 0;
      SetScrollRange(Handle, SB_HORZ, 0, Range, redraw);
    end
    else begin
      SetAxisRange(FMaxTopLeft.X, OldTopLeft.X, FTopLeft.X,
        SB_HORZ, FixedCols, redraw);
      hscroll := FMaxTopLeft.X > FixedCols;
    end;
  end;

  procedure SetVertRange(redraw: Boolean);
  var
    Range: Integer;
  begin
    if (RowCount = 1) then
    begin
      Range := RowHeights[0] - ClientHeight;
      if (Range < 0) then Range := 0;
      vscroll := Range > 0;
      SetScrollRange(Handle, SB_VERT, 0, Range, redraw);
    end
    else begin
      SetAxisRange(FMaxTopLeft.Y, OldTopLeft.Y, FTopLeft.Y,
        SB_VERT, FixedRows, redraw);
      vscroll := FMaxTopLeft.Y > FixedRows;
    end;
  end;

begin
  if (not HandleAllocated) then Exit;
  OldTopLeft := FTopLeft;
  with FCurrentDrawInfo do
  begin
    if (ssNone = ScrollBars) then
    begin
      CalcDrawInfoXY(FCurrentDrawInfo, ClientWidth, ClientHeight);
      MaxTopLeft.X := ColCount - 1;
      MaxTopLeft.Y := RowCount - 1;
      MaxTopLeft := CalcMaxTopLeft(MaxTopLeft, FCurrentDrawInfo);
      Exit;
    end
    else if (ssHorizontal = ScrollBars) then
    begin
      CalcHorzSizeInfo(ClientWidth);
      SetHorzRange(True);
      CalcVertSizeInfo(ClientHeight);
    end
    else if (ssVertical = ScrollBars) then
    begin
      CalcVertSizeInfo(ClientHeight);
      SetVertRange(True);
      CalcHorzSizeInfo(ClientWidth);
    end
    else begin
      hscroll := False;
      vscroll := False;
      Horz.GridExtent := ClientWidth;
      if ScrollBarVisible(SB_VERT) then
        Inc(Horz.GridExtent, GetSystemMetrics(SM_CXVSCROLL));
      CalcHorzSizeInfo(Horz.GridExtent);
      SetHorzRange(True);
      if (hscroll) then
      begin
        CalcVertSizeInfo(ClientHeight);
        SetVertRange(True);
        if (vscroll) then
        begin
          CalcHorzSizeInfo(ClientWidth);
          SetHorzRange(True);
          CalcVertSizeInfo(ClientHeight);
          SetVertRange(True);
        end;
      end
      else begin
        CalcVertSizeInfo(ClientHeight);
        SetVertRange(True);
        if (vscroll) then
        begin
          CalcHorzSizeInfo(ClientWidth);
          SetHorzRange(True);
          if (hscroll) then
          begin
            CalcHorzSizeInfo(ClientWidth);
            SetHorzRange(True);
            CalcVertSizeInfo(ClientHeight);
            SetVertRange(True);
          end;
        end;
      end;
    end;
    UpdateScrollPos;
    if (FTopLeft.X <> OldTopLeft.X) or (FTopLeft.Y <> OldTopLeft.Y) then
      TopLeftMoved(OldTopLeft);
  end;
end;

procedure TDzCustomGrid.UpdateText;
begin
  if (FInplaceCol <> -1) and (FInplaceRow <> -1) then
    SetEditText(FInplaceCol, FInplaceRow, FInplaceEdit.Text);
end;

procedure TDzCustomGrid.WMChar(var Msg: TWMChar);
begin
  if (goEditing in Options) and (Char(Msg.CharCode) in [^H, #32..#255]) then
    ShowEditorChar(Char(Msg.CharCode))
  else
    inherited;
end;

procedure TDzCustomGrid.WMCommand(var Message: TWMCommand);
begin
  with Message do
  begin
    if (FInplaceEdit <> nil) and (Ctl = FInplaceEdit.Handle) then
      case NotifyCode of
        EN_CHANGE: UpdateText;
      end;
  end;
end;

procedure TDzCustomGrid.WMCreate(var Msg: TWMCreate);
begin
  inherited;
  //FMaxTopLeft.X := -1;
end;

procedure TDzCustomGrid.WMGetDlgCode(var Msg: TWMGetDlgCode);
begin
  Msg.Result := DLGC_WANTARROWS;
  if goRowSelect in Options then
    Exit;
  if goTabs in Options then
    Msg.Result := Msg.Result or DLGC_WANTTAB;
  if goEditing in Options then
    Msg.Result := Msg.Result or DLGC_WANTCHARS;
end;

procedure TDzCustomGrid.WMKillFocus(var Msg: TWMKillFocus);
begin
  inherited;
  DestroyCaret;
  InvalidateRect(Selection);
  if (FInplaceEdit <> nil) and (Msg.FocusedWnd <> FInplaceEdit.Handle) then
    HideEdit;
end;

procedure TDzCustomGrid.WMLButtonDown(var Message: TMessage);
begin
  inherited;
  if FInplaceEdit <> nil then
    FInplaceEdit.FClickTime := GetMessageTime;
end;

procedure TDzCustomGrid.WMNCHitTest(var Msg: TWMNCHitTest);
begin
  DefaultHandler(Msg);
  FHitTest := ScreenToClient(SmallPointToPoint(Msg.Pos));
end;

procedure TDzCustomGrid.WMSetCursor(var Msg: TWMSetCursor);
var
  State: TGridState;
  Index: Longint;
  Pos, Ofs: Integer;
  Cur: HCURSOR;
begin
  Cur := 0;
  with Msg, FHitTest do
  begin
    if (HitTest = HTCLIENT) then
    begin
      if FGridState = gsNormal then
        CalcSizingState(X, Y, State, Index, Pos, Ofs, FCurrentDrawInfo)
      else
        State := FGridState;
      if (State = gsRowSizing) then
        Cur := Screen.Cursors[crVSplit]
      else if State = gsColSizing then
        Cur := Screen.Cursors[crHSplit]
    end;
  end;
  if Cur <> 0 then
    SetCursor(Cur)
  else
    inherited;
end;

procedure TDzCustomGrid.WMSetFocus(var Msg: TWMSetFocus);
begin
  inherited;
  CreateCaret(Handle, 0, 0, 0);
  if (FInplaceEdit = nil) or (Msg.FocusedWnd <> FInplaceEdit.Handle) then
  begin
    InvalidateRect(Selection);
    UpdateEdit;
  end;
end;

procedure TDzCustomGrid.WMSize(var Msg: TWMSize);
begin
  inherited;
  UpdateMaxTopLeft;
  if UseRightToLeftAlignment then Invalidate;
end;

function GetScrollTrackPos(hwnd: THandle; bar: Integer): Integer;
var
  si: TScrollInfo;
begin
  si.cbSize := SizeOf(si);
  si.fMask := SIF_TRACKPOS;
  GetScrollInfo(hwnd, bar, si);
  Result := si.nTrackPos;
end;

procedure TDzCustomGrid.WMVScroll(var Msg: TWMVScroll);
begin
  ModifyScrollBar(SB_VERT, Msg.ScrollCode,
    GetScrollTrackPos(Handle, SB_VERT), True);
end;

procedure TDzCustomGrid.WMHScroll(var Msg: TWMHScroll);
begin
  ModifyScrollBar(SB_HORZ, Msg.ScrollCode,
    GetScrollTrackPos(Handle, SB_HORZ), True);
end;

procedure TDzCustomGrid.CancelMode;
begin
  try
    case FGridState of
      gsSelecting: KillTimer(Handle, 1);
      gsRowSizing, gsColSizing: DrawSizingLine(FCurrentDrawInfo);
      gsColMoving, gsRowMoving:
        begin
          DrawMove;
          KillTimer(Handle, 1);
        end;
    end;
  finally
    FGridState := gsNormal;
  end;
end;

function TDzCustomGrid.CanDoSelect(ACol, ARow: Integer): Boolean;
begin
  Result := True;
end;

procedure TDzCustomGrid.WMCancelMode(var Msg: TWMCancelMode);
begin
  inherited;
  CancelMode;
end;

procedure TDzCustomGrid.CMCancelMode(var Msg: TMessage);
begin
  if Assigned(FInplaceEdit) then
    FInplaceEdit.WndProc(Msg);
  inherited;
  CancelMode;
end;

procedure TDzCustomGrid.CMFontChanged(var Message: TMessage);
begin
  if FInplaceEdit <> nil then
    FInplaceEdit.Font := Font;
  inherited;
end;

procedure TDzCustomGrid.CMCtl3DChanged(var Message: TMessage);
begin
  inherited;
  RecreateWnd;
end;

procedure TDzCustomGrid.CMDesignHitTest(var Msg: TCMDesignHitTest);
begin
  Msg.Result := Longint(BOOL(Sizing(Msg.Pos.X, Msg.Pos.Y)));
end;

procedure TDzCustomGrid.CMWantSpecialKey(var Msg: TCMWantSpecialKey);
begin
  inherited;
  if (goEditing in Options) and (Char(Msg.CharCode) = #13) then
    Msg.Result := 1;
end;

procedure TDzCustomGrid.TimedScroll(Direction: TGridScrollDirection);
var
  NewAnchor: TGridCoord;
begin
  NewAnchor := fAnchor;
  if (sdLeft in Direction) and (fAnchor.X > FixedCols) then Dec(NewAnchor.X);
  if (sdRight in Direction) and (fAnchor.X < ColCount - 1) then Inc(NewAnchor.X);
  if (sdUp in Direction) and (fAnchor.Y > FixedRows) then Dec(NewAnchor.Y);
  if (sdDown in Direction) and (fAnchor.Y < RowCount - 1) then Inc(NewAnchor.Y);
  if (fAnchor.X <> NewAnchor.X) or (fAnchor.Y <> NewAnchor.Y) then
    MoveAnchor(NewAnchor);
end;

procedure TDzCustomGrid.WMTimer(var Msg: TWMTimer);
var
  Point: TPoint;
  ScrollDirection: TGridScrollDirection;
  CellHit: TGridCoord;
  LeftSide: Integer;
  RightSide: Integer;
begin
  if not (FGridState in [gsSelecting, gsRowMoving, gsColMoving]) then Exit;
  GetCursorPos(Point);
  Point := ScreenToClient(Point);
  ScrollDirection := [];
  with FCurrentDrawInfo do
  begin
    CellHit := MouseCoord(Point.X, Point.Y);
    case FGridState of
      gsColMoving:
        MoveAndScroll(Point.X, CellHit.X, FCurrentDrawInfo,
          Horz, SB_HORZ, Point);
      gsRowMoving:
        MoveAndScroll(Point.Y, CellHit.Y, FCurrentDrawInfo,
          Vert, SB_VERT, Point);
      gsSelecting:
        begin
          if UseRightToLeftAlignment then
          begin
            LeftSide := ClientWidth - Horz.FullVisBoundary;
            RightSide := ClientWidth - Horz.FixedBoundary;
            if Point.X < LeftSide then
              Include(ScrollDirection, sdRight)
            else if Point.X > RightSide then
              Include(ScrollDirection, sdLeft);
          end
          else begin
            if Point.X < Horz.FixedBoundary then
              Include(ScrollDirection, sdLeft)
            else if Point.X > Horz.FullVisBoundary then
              Include(ScrollDirection, sdRight);
          end;
          if Point.Y < Vert.FixedBoundary then Include(ScrollDirection, sdUp)
          else if Point.Y > Vert.FullVisBoundary then
            Include(ScrollDirection, sdDown);
          if ScrollDirection <> [] then TimedScroll(ScrollDirection);
        end;
    end;
  end;
end;

procedure TDzCustomGrid.ColWidthsChanged(Index: Integer);
begin
  UpdateMaxTopLeft;
  UpdateEdit;
end;

procedure TDzCustomGrid.RowHeightsChanged;
begin
  UpdateMaxTopLeft;
  UpdateEdit;
end;

procedure TDzCustomGrid.DeleteColumn(ACol: Longint);
begin
  MoveColumn(ACol, ColCount - 1);
  ColCount := ColCount - 1;
end;

procedure TDzCustomGrid.DeleteRow(ARow: Longint);
begin
  MoveRow(ARow, RowCount - 1);
  RowCount := RowCount - 1;
end;

procedure TDzCustomGrid.UpdateDesigner;
var
  ParentForm: TCustomForm;
begin
  if (csDesigning in ComponentState) and HandleAllocated and
    not (csUpdating in ComponentState) then
  begin
    ParentForm := GetParentForm(Self);
    if Assigned(ParentForm) and Assigned(ParentForm.Designer) then
      ParentForm.Designer.Modified;
  end;
end;

procedure TDzCustomGrid.UpdateDrawInfo;
begin
  CalcDrawInfo(FCurrentDrawInfo);
end;

function TDzCustomGrid.CheckColumnDrag(var Origin,
  Destination: Integer; const MousePt: TPoint): Boolean;
begin
  Result := True;
end;

function TDzCustomGrid.CheckRowDrag(var Origin,
  Destination: Integer; const MousePt: TPoint): Boolean;
begin
  Result := True;
end;

function TDzCustomGrid.AllSelected: Boolean;
begin
  Result := FSelRows.Count = RowCount - FixedRows;
end;

function TDzCustomGrid.BeginColumnDrag(var Origin, Destination: Integer;
  const MousePt: TPoint): Boolean;
begin
  Result := True;
end;

function TDzCustomGrid.BeginRowDrag(var Origin, Destination: Integer;
  const MousePt: TPoint): Boolean;
begin
  Result := True;
end;

function TDzCustomGrid.EndColumnDrag(var Origin, Destination: Integer;
  const MousePt: TPoint): Boolean;
begin
  Result := True;
end;

function TDzCustomGrid.EndRowDrag(var Origin, Destination: Integer; const
  MousePt: TPoint): Boolean;
begin
  Result := True;
end;

procedure TDzCustomGrid.CMShowingChanged(var Message: TMessage);
begin
  inherited;
  if Showing then UpdateMaxTopLeft;
end;

{ TCustomDrawGrid }

function TCustomDrawGrid.CellBkColor(ACol, ARow: Integer;
  const DrawState: TGridDrawState): TColor;
begin
  Result := inherited CellBkColor(ACol, ARow, DrawState);
  if Assigned(FOnBkColor) then
    FOnBkColor(Self, ACol, ARow, DrawState, Result);
end;

function TCustomDrawGrid.CellRect(ACol, ARow: Longint): TRect;
begin
  Result := inherited CellRect(ACol, ARow);
end;

procedure TCustomDrawGrid.MouseToCell(X, Y: Integer; var ACol, ARow: Longint);
var
  Coord: TGridCoord;
begin
  Coord := MouseCoord(X, Y);
  ACol := Coord.X;
  ARow := Coord.Y;
end;

procedure TCustomDrawGrid.ColumnMoved(FromIndex, ToIndex: Longint);
begin
  inherited;
  if Assigned(FOnColumnMoved) then
    FOnColumnMoved(Self, FromIndex, ToIndex);
end;

function TCustomDrawGrid.GetEditMask(ACol, ARow: Longint): string;
begin
  Result := '';
  if Assigned(FOnGetEditMask) then
    FOnGetEditMask(Self, ACol, ARow, Result);
end;

function TCustomDrawGrid.GetEditText(ACol, ARow: Longint): string;
begin
  Result := '';
  if Assigned(FOnGetEditText) then
    FOnGetEditText(Self, ACol, ARow, Result);
end;

procedure TCustomDrawGrid.RowMoved(FromIndex, ToIndex: Longint);
begin
  if Assigned(FOnRowMoved) then
    FOnRowMoved(Self, FromIndex, ToIndex);
end;

function TCustomDrawGrid.SelectCell(ACol, ARow: Longint): Boolean;
begin
  Result := True;
  if Assigned(FOnSelectCell) then
    FOnSelectCell(Self, ACol, ARow, Result);
end;

procedure TCustomDrawGrid.SetEditText(ACol, ARow: Longint; const Value:
  string);
begin
  if Assigned(FOnSetEditText) then
    FOnSetEditText(Self, ACol, ARow, Value);
end;

procedure TCustomDrawGrid.DrawDataCell(ARow: Integer; AColumn: TDzGridColumn;
  ARect: TRect; AState: TGridDrawState);
var
  Hold: Integer;
begin
  inherited;
  if Assigned(FOnDrawCell) then
  begin
    if UseRightToLeftAlignment then
    begin
      ARect.Left := ClientWidth - ARect.Left;
      ARect.Right := ClientWidth - ARect.Right;
      Hold := ARect.Left;
      ARect.Left := ARect.Right;
      ARect.Right := Hold;
      ChangeGridOrientation(False);
    end;
    FOnDrawCell(Self, ARow, AColumn, ARect, AState);
    if UseRightToLeftAlignment then
    ChangeGridOrientation(True);
  end;
end;

procedure TCustomDrawGrid.TopLeftChanged;
begin
  inherited;
  if Assigned(FOnTopLeftChanged) then FOnTopLeftChanged(Self);
end;

{ TDzGridColumns }

function TDzGridColumns.Add: TDzGridColumn;
begin
  Result := AddItem(nil, Count);
end;

function TDzGridColumns.AddItem(Item: TDzGridColumn;
  Index: Integer): TDzGridColumn;
begin
  if Item = nil then
  begin
    if fGrid = nil then Result := TDzGridColumn(inherited Add)
    else Result := fGrid.CreateColumn;
  end
  else begin
    Result := Item;
    Result.Collection := Self;
  end;
  if Assigned(Result) then
  begin
    if Index < 0 then Index := Count - 1;
    Result.Index := Index;
  end;
end;

function TDzGridColumns.ColumnByName(const ColName: string): TDzGridColumn;
var
  i: Integer;
begin
  for i := 0 to Self.Count - 1 do
  begin
    Result := Items[i];
    if SameText(Result.FieldName, ColName) then Exit;
  end;
  Result := nil;
end;

constructor TDzGridColumns.Create(Grid: TDzCustomColumnGrid);
begin
  if Assigned(Grid) then
    inherited Create(Grid.GetColumnClass)
  else
    inherited Create(TDzGridColumn);
  fGrid := Grid;
end;

function TDzGridColumns.GetItem(Index: Integer): TDzGridColumn;
begin
  Result := TDzGridColumn(inherited Items[Index]);
end;

function TDzGridColumns.GetOwner: Classes.TPersistent;
begin
  Result := fGrid;
end;

function TDzGridColumns.Insert(Index: Integer): TDzGridColumn;
begin
  Result := AddItem(nil, Index);
end;

procedure TDzGridColumns.SetItem(Index: Integer; Value: TDzGridColumn);
begin
  inherited Items[Index] := Value;
end;

procedure TDzGridColumns.Update(Item: TCollectionItem);
begin
  inherited;
  if fGrid = nil then Exit;
  if Item <> nil then fGrid.UpdateColumn(Item.Index)
  else fGrid.UpdateColumns;
end;

{ TDzGridColumn }

procedure TDzGridColumn.Assign(Source: Classes.TPersistent);
begin
  AssignObject(Source, Self);
  if Source is TDzGridColumn then
  begin
    Self.FieldInfo := TDzGridColumn(Source).FieldInfo;
  end;
end;

constructor TDzGridColumn.Create(Collection: TCollection);
begin
  if (Collection <> nil) and
    (TDzGridColumns(Collection).fGrid <> nil) then
    fWidth := TDzGridColumns(Collection).fGrid.DefaultColWidth;
  inherited;
end;

function TDzGridColumn.GetDisplayName: string;
begin
  Result := Caption;
  if Result = '' then Result := inherited GetDisplayName;
end;

procedure TDzGridColumn.SetCaption(const Value: string);
begin
  if fCaption <> Value then
  begin
    fCaption := Value;
    Changed(False);
  end;
end;

procedure TDzGridColumn.SetContentAlign(const Value: TAlignment);
begin
  if fContentAlign <> Value then
  begin
    fContentAlign := Value;
    Changed(False);
  end;
end;

procedure TDzGridColumn.SetContentType(const Value: TDzGridColumnContentType);
begin
  if fContentType <> Value then
  begin
    fContentType := Value;
    Changed(False);
  end;
end;

procedure TDzGridColumn.SetFieldName(const Value: string);
begin
  if fFieldName <> Value then
  begin
    fFieldName := Value;
    Changed(False);
  end;
end;

procedure TDzGridColumn.SetHint(const Value: string);
begin
  if fHint <> Value then fHint := Value;
end;

procedure TDzGridColumn.SetSortDirection(const Value: TSortDirection);
begin
  if fSortDirection <> Value then
  begin
    fSortDirection := Value;
    Changed(False);
  end;
end;

procedure TDzGridColumn.SetTitleAlign(const Value: TAlignment);
begin
  if fTitleAlign <> Value then
  begin
    fTitleAlign := Value;
    Changed(False);
  end;
end;

procedure TDzGridColumn.SetWidth(const Value: Integer);
begin
  if fWidth <> Value then
  begin
    fWidth := Value;
    Changed(False);
  end;
end;

{ TDzCustomColumnGrid }

function TDzCustomColumnGrid.CanDoSelect(ACol, ARow: Integer): Boolean;
begin
  Result := True;
  if Assigned(fOnBeforeRowSelect) then
    fOnBeforeRowSelect(Self, Columns[ACol], ARow, Result);
end;

procedure TDzCustomColumnGrid.ClickCell(ACol, ARow: Integer);
var
  column: TDzGridColumn;
begin
  if ARow < FixedRows then
  begin
    column := Columns[ACol];
    ClickColumn(column);
  end
  else inherited;
end;

procedure TDzCustomColumnGrid.ClickColumn(column: TDzGridColumn);
begin
  if Assigned(fOnClickColumn) then
    fOnClickColumn(Self, column);
end;

procedure TDzCustomColumnGrid.ColumnMoved(FromIndex, ToIndex: Integer);
var
  temp: TDzGridColumn;
begin
  inherited;
  temp := TDzGridColumn.Create(nil);
  fColumns.BeginUpdate;
  try
    temp.Assign(fColumns[FromIndex]);
    fColumns[FromIndex].Assign(fColumns[ToIndex]);
    fColumns[ToIndex].Assign(temp);
  finally
    temp.Free;
    fColumns.EndUpdate;  
  end;
end;

procedure TDzCustomColumnGrid.ColWidthsChanged(Index: Integer);
var
  i: Integer;
begin
  inherited;
  if (Index = -1) then
  begin
    for i := 0 to fColumns.Count - 1 do
      fColumns[Index].fWidth := ColWidths[i];
  end
  else if (Index < fColumns.Count) then
  begin
    fColumns[Index].Width := ColWidths[Index];
  end;
end;

constructor TDzCustomColumnGrid.Create(AOwner: TComponent);
begin
  inherited;
  fColumns := CreateColumns;
  RowCount := 2;
  FixedRows := 1;
  fColumns.AddItem(nil, 0);
  fColumns.AddItem(nil, 0);
  FixedCols := 0;
  fColumns[0].Width := 20;
end;

function TDzCustomColumnGrid.CreateColumn: TDzGridColumn;
var
  colcls: TDzGridColumnClass;
begin
  colcls := GetColumnClass;
  if Assigned(fOnCreateColumnClass) then
    fOnCreateColumnClass(Self, colcls);
  Result := colcls.Create(fColumns);
  Result.Width := Self.DefaultColWidth;
end;

function TDzCustomColumnGrid.CreateColumns: TDzGridColumns;
begin
  Result := TDzGridColumns.Create(Self);
end;

destructor TDzCustomColumnGrid.Destroy;
begin
  fColumns.Free;
  inherited;
end;

procedure TDzCustomColumnGrid.DrawCell(ACol, ARow: Integer;
  ARect: TRect; AState: TGridDrawState);
var
  indictop: Integer;
  text: string;
  drawFlags: LongWord;
  column: TDzGridColumn;
  clr: TColor;
  flag: DWORD;
  function GetCheckBoxRect(const Rect: TRect; width: Integer): TRect;
  var
    x, y: Integer;
    procedure SetCenter(min, max, len: Integer; var _min, _max: Integer);
    begin
      if (max - min > len) then
      begin
        _min := min + (max - min - len) div 2;
        _max := max - (max - min - len) div 2;
      end
      else begin
        _min := min + 1;
        _max := max - 1;
      end;
    end;
  begin
    x := Rect.Right - Rect.Left;
    y := Rect.Bottom - Rect.Top;
    if (x > y) then
    begin
      SetCenter(Rect.Top, Rect.Bottom, width, Result.Top, Result.Bottom);
      Result.Left := Rect.Left;
      Result.Right := Rect.Right;
    end
    else begin
      SetCenter(Rect.Left, Rect.Right, width, Result.Left, Result.Right);
      Result.Top := Rect.Top;
      Result.Bottom := Rect.Bottom;
    end;
  end;
const
  HorzAlign: array [TAlignment] of LongWord = (DT_LEFT, DT_RIGHT, DT_CENTER);
begin
  column := Columns[ACol];
  drawFlags := DT_SINGLELINE or DT_VCENTER or DT_END_ELLIPSIS;
  with Canvas do
  if (ARow < FixedRows) then
  begin
    if column.ContentType = gcctCheckBox then
    begin
      if Ctl3D then flag := 0
      else flag := DFCS_FLAT;
      if FSelRows.Count = RowCount - FixedRows then
        flag := flag or DFCS_CHECKED;
      DrawFrameControl(Handle, GetCheckBoxRect(ARect, 16),
        DFC_BUTTON, flag or DFCS_BUTTONCHECK);
    end
    else begin
      clr := Pen.Color;
      Brush.Style := bsSolid;
      Brush.Color := DWORD(FixedColor) xor $ffffffff;
      Pen.Color := Brush.Color;
      indictop := ARect.Top + (ARect.Bottom - ARect.Top - 8) div 2;
      case column.SortDirection of
        sdNone: ARect.Left := ARect.Left + 5;
        sdAscendent:
          begin
            Polygon([Point(ARect.Left + 9, indictop),
              Point(ARect.Left + 2, indictop + 8),
              Point(ARect.Left + 16, indictop + 8)]);
            ARect.Left := ARect.Left + 20;
          end;
        sdDescendent:
          begin
            Polygon([Point(ARect.Left + 2, indictop),
              Point(ARect.Left + 16, indictop),
              Point(ARect.Left + 9, indictop + 8)]);
            ARect.Left := ARect.Left + 20;
          end;
      end;
      Pen.Color := clr;
      text := column.Caption;
      Brush.Style := bsClear;
      DrawText(Handle, PChar(text), Length(text), ARect, DrawFlags or
        HorzAlign[column.TitleAlign]);
    end;
  end
  else DrawDataCell(ARow, column, ARect, AState);
end;

function TDzCustomColumnGrid.GetColumnClass: TDzGridColumnClass;
begin
  Result := TDzGridColumn;
end;

procedure TDzCustomColumnGrid.SelectionChanged;
var
  r, c: Integer;
begin
  inherited;
  for r := 0 to FixedRows - 1 do
    for c := 0 to FixedCols - 1 do
      InvalidateCell(c, r);
end;

procedure TDzCustomColumnGrid.SetColumns(const Value: TDzGridColumns);
begin
  fColumns.Assign(Value);
end;

procedure TDzCustomColumnGrid.UpdateColumn(Index: Integer);
begin
  if (ColWidths[Index] <> fColumns[Index].Width) then
    ColWidths[Index] := fColumns[Index].Width
  else
    InvalidateCell(Index, 0);
end;

procedure TDzCustomColumnGrid.UpdateColumns;
var
  i: Integer;
begin
  ColCount := fColumns.Count;
  for i := 0 to fColumns.Count - 1 do
    ColWidths[i] := fColumns[i].Width;  
end;

end.

